Update lego
This commit is contained in:
parent
65284441fa
commit
a3b95f798b
61 changed files with 3453 additions and 1536 deletions
170
vendor/github.com/xenolf/lego/acme/client.go
generated
vendored
170
vendor/github.com/xenolf/lego/acme/client.go
generated
vendored
|
@ -11,6 +11,7 @@ import (
|
|||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
@ -22,6 +23,16 @@ var (
|
|||
Logger *log.Logger
|
||||
)
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
// logf writes a log entry. It uses Logger if not
|
||||
// nil, otherwise it uses the default log.Logger.
|
||||
func logf(format string, args ...interface{}) {
|
||||
|
@ -518,7 +529,11 @@ func (c *Client) chooseSolvers(auth authorization, domain string) map[int]solver
|
|||
func (c *Client) getChallenges(domains []string) ([]authorizationResource, map[string]error) {
|
||||
resc, errc := make(chan authorizationResource), make(chan domainError)
|
||||
|
||||
delay := time.Second / overallRequestLimit
|
||||
|
||||
for _, domain := range domains {
|
||||
time.Sleep(delay)
|
||||
|
||||
go func(domain string) {
|
||||
authMsg := authorization{Resource: "new-authz", Identifier: identifier{Type: "dns", Value: domain}}
|
||||
var authz authorization
|
||||
|
@ -531,6 +546,7 @@ func (c *Client) getChallenges(domains []string) ([]authorizationResource, map[s
|
|||
links := parseLinks(hdr["Link"])
|
||||
if links["next"] == "" {
|
||||
logf("[ERROR][%s] acme: Server did not provide next link to proceed", domain)
|
||||
errc <- domainError{Domain: domain, Error: errors.New("Server did not provide next link to proceed")}
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -556,12 +572,20 @@ func (c *Client) getChallenges(domains []string) ([]authorizationResource, map[s
|
|||
}
|
||||
}
|
||||
|
||||
logAuthz(challenges)
|
||||
|
||||
close(resc)
|
||||
close(errc)
|
||||
|
||||
return challenges, failures
|
||||
}
|
||||
|
||||
func logAuthz(authz []authorizationResource) {
|
||||
for _, auth := range authz {
|
||||
logf("[INFO][%s] AuthURL: %s", auth.Domain, auth.AuthURL)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) requestCertificate(authz []authorizationResource, bundle bool, privKey crypto.PrivateKey, mustStaple bool) (CertificateResource, error) {
|
||||
if len(authz) == 0 {
|
||||
return CertificateResource{}, errors.New("Passed no authorizations to requestCertificate!")
|
||||
|
@ -610,73 +634,95 @@ func (c *Client) requestCertificateForCsr(authz []authorizationResource, bundle
|
|||
return CertificateResource{}, err
|
||||
}
|
||||
|
||||
cerRes := CertificateResource{
|
||||
certRes := CertificateResource{
|
||||
Domain: commonName.Domain,
|
||||
CertURL: resp.Header.Get("Location"),
|
||||
PrivateKey: privateKeyPem}
|
||||
PrivateKey: privateKeyPem,
|
||||
}
|
||||
|
||||
for {
|
||||
switch resp.StatusCode {
|
||||
case 201, 202:
|
||||
cert, err := ioutil.ReadAll(limitReader(resp.Body, 1024*1024))
|
||||
resp.Body.Close()
|
||||
if err != nil {
|
||||
return CertificateResource{}, err
|
||||
}
|
||||
|
||||
// The server returns a body with a length of zero if the
|
||||
// certificate was not ready at the time this request completed.
|
||||
// Otherwise the body is the certificate.
|
||||
if len(cert) > 0 {
|
||||
|
||||
cerRes.CertStableURL = resp.Header.Get("Content-Location")
|
||||
cerRes.AccountRef = c.user.GetRegistration().URI
|
||||
|
||||
issuedCert := pemEncode(derCertificateBytes(cert))
|
||||
|
||||
// The issuer certificate link is always supplied via an "up" link
|
||||
// in the response headers of a new certificate.
|
||||
links := parseLinks(resp.Header["Link"])
|
||||
issuerCert, err := c.getIssuerCertificate(links["up"])
|
||||
if err != nil {
|
||||
// If we fail to acquire the issuer cert, return the issued certificate - do not fail.
|
||||
logf("[WARNING][%s] acme: Could not bundle issuer certificate: %v", commonName.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 {
|
||||
issuedCert = append(issuedCert, issuerCert...)
|
||||
}
|
||||
}
|
||||
|
||||
cerRes.Certificate = issuedCert
|
||||
cerRes.IssuerCertificate = issuerCert
|
||||
logf("[INFO][%s] Server responded with a certificate.", commonName.Domain)
|
||||
return cerRes, nil
|
||||
}
|
||||
|
||||
// The certificate was granted but is not yet issued.
|
||||
// Check retry-after and loop.
|
||||
ra := resp.Header.Get("Retry-After")
|
||||
retryAfter, err := strconv.Atoi(ra)
|
||||
if err != nil {
|
||||
return CertificateResource{}, err
|
||||
}
|
||||
|
||||
logf("[INFO][%s] acme: Server responded with status 202; retrying after %ds", commonName.Domain, retryAfter)
|
||||
time.Sleep(time.Duration(retryAfter) * time.Second)
|
||||
|
||||
break
|
||||
default:
|
||||
return CertificateResource{}, handleHTTPError(resp)
|
||||
}
|
||||
|
||||
resp, err = httpGet(cerRes.CertURL)
|
||||
maxChecks := 1000
|
||||
for i := 0; i < maxChecks; i++ {
|
||||
done, err := c.checkCertResponse(resp, &certRes, bundle)
|
||||
resp.Body.Close()
|
||||
if err != nil {
|
||||
return CertificateResource{}, err
|
||||
}
|
||||
if done {
|
||||
break
|
||||
}
|
||||
if i == maxChecks-1 {
|
||||
return CertificateResource{}, fmt.Errorf("polled for certificate %d times; giving up", i)
|
||||
}
|
||||
resp, err = httpGet(certRes.CertURL)
|
||||
if err != nil {
|
||||
return CertificateResource{}, err
|
||||
}
|
||||
}
|
||||
|
||||
return certRes, nil
|
||||
}
|
||||
|
||||
// checkCertResponse checks resp to see if a certificate is contained in the
|
||||
// response, and if so, loads it into certRes and returns true. If the cert
|
||||
// is not yet ready, it returns false. This function honors the waiting period
|
||||
// required by the Retry-After header of the response, if specified. This
|
||||
// function may read from resp.Body but does NOT close it. 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(resp *http.Response, certRes *CertificateResource, bundle bool) (bool, error) {
|
||||
switch resp.StatusCode {
|
||||
case 201, 202:
|
||||
cert, err := ioutil.ReadAll(limitReader(resp.Body, maxBodySize))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// The server returns a body with a length of zero if the
|
||||
// certificate was not ready at the time this request completed.
|
||||
// Otherwise the body is the certificate.
|
||||
if len(cert) > 0 {
|
||||
certRes.CertStableURL = resp.Header.Get("Content-Location")
|
||||
certRes.AccountRef = c.user.GetRegistration().URI
|
||||
|
||||
issuedCert := pemEncode(derCertificateBytes(cert))
|
||||
|
||||
// The issuer certificate link is always supplied via an "up" link
|
||||
// in the response headers of a new certificate.
|
||||
links := parseLinks(resp.Header["Link"])
|
||||
issuerCert, err := c.getIssuerCertificate(links["up"])
|
||||
if err != nil {
|
||||
// If we fail to acquire the issuer cert, return the issued certificate - do not fail.
|
||||
logf("[WARNING][%s] acme: Could not bundle issuer certificate: %v", certRes.Domain, err)
|
||||
} 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 {
|
||||
issuedCert = append(issuedCert, issuerCert...)
|
||||
}
|
||||
}
|
||||
|
||||
certRes.Certificate = issuedCert
|
||||
certRes.IssuerCertificate = issuerCert
|
||||
logf("[INFO][%s] Server responded with a certificate.", certRes.Domain)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// The certificate was granted but is not yet issued.
|
||||
// Check retry-after and loop.
|
||||
ra := resp.Header.Get("Retry-After")
|
||||
retryAfter, err := strconv.Atoi(ra)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
logf("[INFO][%s] acme: Server responded with status 202; retrying after %ds", certRes.Domain, retryAfter)
|
||||
time.Sleep(time.Duration(retryAfter) * time.Second)
|
||||
|
||||
return false, nil
|
||||
default:
|
||||
return false, handleHTTPError(resp)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -689,7 +735,7 @@ func (c *Client) getIssuerCertificate(url string) ([]byte, error) {
|
|||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
issuerBytes, err := ioutil.ReadAll(limitReader(resp.Body, 1024*1024))
|
||||
issuerBytes, err := ioutil.ReadAll(limitReader(resp.Body, maxBodySize))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
22
vendor/github.com/xenolf/lego/acme/error.go
generated
vendored
22
vendor/github.com/xenolf/lego/acme/error.go
generated
vendored
|
@ -10,6 +10,7 @@ import (
|
|||
|
||||
const (
|
||||
tosAgreementError = "Must agree to subscriber agreement before any further actions"
|
||||
invalidNonceError = "JWS has invalid anti-replay nonce"
|
||||
)
|
||||
|
||||
// RemoteError is the base type for all errors specific to the ACME protocol.
|
||||
|
@ -30,6 +31,12 @@ 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
|
||||
|
@ -54,20 +61,17 @@ func (c challengeError) Error() string {
|
|||
func handleHTTPError(resp *http.Response) error {
|
||||
var errorDetail RemoteError
|
||||
|
||||
contenType := resp.Header.Get("Content-Type")
|
||||
// try to decode the content as JSON
|
||||
if contenType == "application/json" || contenType == "application/problem+json" {
|
||||
decoder := json.NewDecoder(resp.Body)
|
||||
err := decoder.Decode(&errorDetail)
|
||||
contentType := resp.Header.Get("Content-Type")
|
||||
if contentType == "application/json" || contentType == "application/problem+json" {
|
||||
err := json.NewDecoder(resp.Body).Decode(&errorDetail)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
detailBytes, err := ioutil.ReadAll(limitReader(resp.Body, 1024*1024))
|
||||
detailBytes, err := ioutil.ReadAll(limitReader(resp.Body, maxBodySize))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
errorDetail.Detail = string(detailBytes)
|
||||
}
|
||||
|
||||
|
@ -78,6 +82,10 @@ func handleHTTPError(resp *http.Response) error {
|
|||
return TOSError{errorDetail}
|
||||
}
|
||||
|
||||
if errorDetail.StatusCode == http.StatusBadRequest && strings.HasPrefix(errorDetail.Detail, invalidNonceError) {
|
||||
return NonceError{errorDetail}
|
||||
}
|
||||
|
||||
return errorDetail
|
||||
}
|
||||
|
||||
|
|
43
vendor/github.com/xenolf/lego/acme/http.go
generated
vendored
43
vendor/github.com/xenolf/lego/acme/http.go
generated
vendored
|
@ -31,14 +31,14 @@ const (
|
|||
func httpHead(url string) (resp *http.Response, err error) {
|
||||
req, err := http.NewRequest("HEAD", url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
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, err
|
||||
return resp, fmt.Errorf("failed to do head %q: %v", url, err)
|
||||
}
|
||||
resp.Body.Close()
|
||||
return resp, err
|
||||
|
@ -49,7 +49,7 @@ func httpHead(url string) (resp *http.Response, err error) {
|
|||
func httpPost(url string, bodyType string, body io.Reader) (resp *http.Response, err error) {
|
||||
req, err := http.NewRequest("POST", url, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("failed to post %q: %v", url, err)
|
||||
}
|
||||
req.Header.Set("Content-Type", bodyType)
|
||||
req.Header.Set("User-Agent", userAgent())
|
||||
|
@ -62,7 +62,7 @@ func httpPost(url string, bodyType string, body io.Reader) (resp *http.Response,
|
|||
func httpGet(url string) (resp *http.Response, err error) {
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("failed to get %q: %v", url, err)
|
||||
}
|
||||
req.Header.Set("User-Agent", userAgent())
|
||||
|
||||
|
@ -74,7 +74,7 @@ func httpGet(url string) (resp *http.Response, err error) {
|
|||
func getJSON(uri string, respBody interface{}) (http.Header, error) {
|
||||
resp, err := httpGet(uri)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get %q: %v", uri, err)
|
||||
return nil, fmt.Errorf("failed to get json %q: %v", uri, err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
|
@ -97,10 +97,41 @@ func postJSON(j *jws, uri string, reqBody, respBody interface{}) (http.Header, e
|
|||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to post JWS message. -> %v", err)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode >= http.StatusBadRequest {
|
||||
return resp.Header, handleHTTPError(resp)
|
||||
|
||||
err := handleHTTPError(resp)
|
||||
|
||||
switch err.(type) {
|
||||
|
||||
case NonceError:
|
||||
|
||||
// Retry once if the nonce was invalidated
|
||||
|
||||
retryResp, err := j.post(uri, jsonBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to post JWS message. -> %v", err)
|
||||
}
|
||||
|
||||
defer retryResp.Body.Close()
|
||||
|
||||
if retryResp.StatusCode >= http.StatusBadRequest {
|
||||
return retryResp.Header, handleHTTPError(retryResp)
|
||||
}
|
||||
|
||||
if respBody == nil {
|
||||
return retryResp.Header, nil
|
||||
}
|
||||
|
||||
return retryResp.Header, json.NewDecoder(retryResp.Body).Decode(respBody)
|
||||
|
||||
default:
|
||||
return resp.Header, err
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if respBody == nil {
|
||||
|
|
96
vendor/github.com/xenolf/lego/acme/jws.go
generated
vendored
96
vendor/github.com/xenolf/lego/acme/jws.go
generated
vendored
|
@ -16,8 +16,7 @@ import (
|
|||
type jws struct {
|
||||
directoryURL string
|
||||
privKey crypto.PrivateKey
|
||||
nonces []string
|
||||
sync.Mutex
|
||||
nonces nonceManager
|
||||
}
|
||||
|
||||
func keyAsJWK(key interface{}) *jose.JsonWebKey {
|
||||
|
@ -32,23 +31,26 @@ func keyAsJWK(key interface{}) *jose.JsonWebKey {
|
|||
}
|
||||
}
|
||||
|
||||
// Posts a JWS signed message to the specified URL
|
||||
// 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(content)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("Failed to sign content -> %s", err.Error())
|
||||
}
|
||||
|
||||
resp, err := httpPost(url, "application/jose+json", bytes.NewBuffer([]byte(signedContent.FullSerialize())))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("Failed to HTTP POST to %s -> %s", url, err.Error())
|
||||
}
|
||||
|
||||
j.Lock()
|
||||
defer j.Unlock()
|
||||
j.getNonceFromResponse(resp)
|
||||
nonce, nonceErr := getNonceFromResponse(resp)
|
||||
if nonceErr == nil {
|
||||
j.nonces.Push(nonce)
|
||||
}
|
||||
|
||||
return resp, err
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (j *jws) signContent(content []byte) (*jose.JsonWebSignature, error) {
|
||||
|
@ -67,49 +69,63 @@ func (j *jws) signContent(content []byte) (*jose.JsonWebSignature, error) {
|
|||
|
||||
signer, err := jose.NewSigner(alg, j.privKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("Failed to create jose signer -> %s", err.Error())
|
||||
}
|
||||
signer.SetNonceSource(j)
|
||||
|
||||
signed, err := signer.Sign(content)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("Failed to sign content -> %s", err.Error())
|
||||
}
|
||||
return signed, nil
|
||||
}
|
||||
|
||||
func (j *jws) getNonceFromResponse(resp *http.Response) error {
|
||||
func (j *jws) Nonce() (string, error) {
|
||||
if nonce, ok := j.nonces.Pop(); ok {
|
||||
return nonce, nil
|
||||
}
|
||||
|
||||
return getNonce(j.directoryURL)
|
||||
}
|
||||
|
||||
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 "", fmt.Errorf("Server did not respond with a proper nonce header.")
|
||||
}
|
||||
|
||||
j.nonces = append(j.nonces, nonce)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (j *jws) getNonce() error {
|
||||
resp, err := httpHead(j.directoryURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return j.getNonceFromResponse(resp)
|
||||
}
|
||||
|
||||
func (j *jws) Nonce() (string, error) {
|
||||
j.Lock()
|
||||
defer j.Unlock()
|
||||
nonce := ""
|
||||
if len(j.nonces) == 0 {
|
||||
err := j.getNonce()
|
||||
if err != nil {
|
||||
return nonce, err
|
||||
}
|
||||
}
|
||||
if len(j.nonces) == 0 {
|
||||
return "", fmt.Errorf("Can't get nonce")
|
||||
}
|
||||
nonce, j.nonces = j.nonces[len(j.nonces)-1], j.nonces[:len(j.nonces)-1]
|
||||
return nonce, nil
|
||||
}
|
||||
|
|
2
vendor/github.com/xenolf/lego/cli.go
generated
vendored
2
vendor/github.com/xenolf/lego/cli.go
generated
vendored
|
@ -205,7 +205,7 @@ Here is an example bash command using the CloudFlare DNS provider:
|
|||
fmt.Fprintln(w, "\tauroradns:\tAURORA_USER_ID, AURORA_KEY, AURORA_ENDPOINT")
|
||||
fmt.Fprintln(w, "\tcloudflare:\tCLOUDFLARE_EMAIL, CLOUDFLARE_API_KEY")
|
||||
fmt.Fprintln(w, "\tdigitalocean:\tDO_AUTH_TOKEN")
|
||||
fmt.Fprintln(w, "\tdnsimple:\tDNSIMPLE_EMAIL, DNSIMPLE_API_KEY")
|
||||
fmt.Fprintln(w, "\tdnsimple:\tDNSIMPLE_EMAIL, DNSIMPLE_OAUTH_TOKEN")
|
||||
fmt.Fprintln(w, "\tdnsmadeeasy:\tDNSMADEEASY_API_KEY, DNSMADEEASY_API_SECRET")
|
||||
fmt.Fprintln(w, "\texoscale:\tEXOSCALE_API_KEY, EXOSCALE_API_SECRET, EXOSCALE_ENDPOINT")
|
||||
fmt.Fprintln(w, "\tgandi:\tGANDI_API_KEY")
|
||||
|
|
5
vendor/github.com/xenolf/lego/providers/dns/azure/azure.go
generated
vendored
5
vendor/github.com/xenolf/lego/providers/dns/azure/azure.go
generated
vendored
|
@ -10,10 +10,11 @@ import (
|
|||
|
||||
"github.com/Azure/azure-sdk-for-go/arm/dns"
|
||||
|
||||
"strings"
|
||||
|
||||
"github.com/Azure/go-autorest/autorest/azure"
|
||||
"github.com/Azure/go-autorest/autorest/to"
|
||||
"github.com/xenolf/lego/acme"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface
|
||||
|
@ -74,7 +75,7 @@ func (c *DNSProvider) Present(domain, token, keyAuth string) error {
|
|||
Name: &relative,
|
||||
RecordSetProperties: &dns.RecordSetProperties{
|
||||
TTL: to.Int64Ptr(60),
|
||||
TXTRecords: &[]dns.TxtRecord{dns.TxtRecord{Value: &[]string{value}}},
|
||||
TxtRecords: &[]dns.TxtRecord{dns.TxtRecord{Value: &[]string{value}}},
|
||||
},
|
||||
}
|
||||
_, err = rsc.CreateOrUpdate(c.resourceGroup, zone, relative, dns.TXT, rec, "", "")
|
||||
|
|
121
vendor/github.com/xenolf/lego/providers/dns/dnsimple/dnsimple.go
generated
vendored
121
vendor/github.com/xenolf/lego/providers/dns/dnsimple/dnsimple.go
generated
vendored
|
@ -5,9 +5,10 @@ package dnsimple
|
|||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/weppos/dnsimple-go/dnsimple"
|
||||
"github.com/dnsimple/dnsimple-go/dnsimple"
|
||||
"github.com/xenolf/lego/acme"
|
||||
)
|
||||
|
||||
|
@ -17,37 +18,50 @@ type DNSProvider struct {
|
|||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for dnsimple.
|
||||
// Credentials must be passed in the environment variables: DNSIMPLE_EMAIL
|
||||
// and DNSIMPLE_API_KEY.
|
||||
// Credentials must be passed in the environment variables: DNSIMPLE_OAUTH_TOKEN.
|
||||
//
|
||||
// See: https://developer.dnsimple.com/v2/#authentication
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
email := os.Getenv("DNSIMPLE_EMAIL")
|
||||
key := os.Getenv("DNSIMPLE_API_KEY")
|
||||
return NewDNSProviderCredentials(email, key)
|
||||
accessToken := os.Getenv("DNSIMPLE_OAUTH_TOKEN")
|
||||
baseUrl := os.Getenv("DNSIMPLE_BASE_URL")
|
||||
|
||||
return NewDNSProviderCredentials(accessToken, baseUrl)
|
||||
}
|
||||
|
||||
// NewDNSProviderCredentials uses the supplied credentials to return a
|
||||
// DNSProvider instance configured for dnsimple.
|
||||
func NewDNSProviderCredentials(email, key string) (*DNSProvider, error) {
|
||||
if email == "" || key == "" {
|
||||
return nil, fmt.Errorf("DNSimple credentials missing")
|
||||
func NewDNSProviderCredentials(accessToken, baseUrl string) (*DNSProvider, error) {
|
||||
if accessToken == "" {
|
||||
return nil, fmt.Errorf("DNSimple OAuth token is missing")
|
||||
}
|
||||
|
||||
return &DNSProvider{
|
||||
client: dnsimple.NewClient(key, email),
|
||||
}, nil
|
||||
client := dnsimple.NewClient(dnsimple.NewOauthTokenCredentials(accessToken))
|
||||
client.UserAgent = "lego"
|
||||
|
||||
if baseUrl != "" {
|
||||
client.BaseURL = baseUrl
|
||||
}
|
||||
|
||||
return &DNSProvider{client: client}, nil
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfil the dns-01 challenge.
|
||||
func (c *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value, ttl := acme.DNS01Record(domain, keyAuth)
|
||||
|
||||
zoneID, zoneName, err := c.getHostedZone(domain)
|
||||
zoneName, err := c.getHostedZone(domain)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
accountID, err := c.getAccountID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
recordAttributes := c.newTxtRecord(zoneName, fqdn, value, ttl)
|
||||
_, _, err = c.client.Domains.CreateRecord(zoneID, *recordAttributes)
|
||||
_, err = c.client.Zones.CreateRecord(accountID, zoneName, *recordAttributes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("DNSimple API call failed: %v", err)
|
||||
}
|
||||
|
@ -64,67 +78,79 @@ func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
accountID, err := c.getAccountID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, rec := range records {
|
||||
_, err := c.client.Domains.DeleteRecord(rec.DomainId, rec.Id)
|
||||
_, err := c.client.Zones.DeleteRecord(accountID, rec.ZoneID, rec.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *DNSProvider) getHostedZone(domain string) (string, string, error) {
|
||||
zones, _, err := c.client.Domains.List()
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("DNSimple API call failed: %v", err)
|
||||
}
|
||||
|
||||
func (c *DNSProvider) getHostedZone(domain string) (string, error) {
|
||||
authZone, err := acme.FindZoneByFqdn(acme.ToFqdn(domain), acme.RecursiveNameservers)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
return "", err
|
||||
}
|
||||
|
||||
var hostedZone dnsimple.Domain
|
||||
for _, zone := range zones {
|
||||
if zone.Name == acme.UnFqdn(authZone) {
|
||||
accountID, err := c.getAccountID()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
zoneName := acme.UnFqdn(authZone)
|
||||
|
||||
zones, err := c.client.Zones.ListZones(accountID, &dnsimple.ZoneListOptions{NameLike: zoneName})
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("DNSimple API call failed: %v", err)
|
||||
}
|
||||
|
||||
var hostedZone dnsimple.Zone
|
||||
for _, zone := range zones.Data {
|
||||
if zone.Name == zoneName {
|
||||
hostedZone = zone
|
||||
}
|
||||
}
|
||||
|
||||
if hostedZone.Id == 0 {
|
||||
return "", "", fmt.Errorf("Zone %s not found in DNSimple for domain %s", authZone, domain)
|
||||
if hostedZone.ID == 0 {
|
||||
return "", fmt.Errorf("Zone %s not found in DNSimple for domain %s", authZone, domain)
|
||||
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%v", hostedZone.Id), hostedZone.Name, nil
|
||||
return hostedZone.Name, nil
|
||||
}
|
||||
|
||||
func (c *DNSProvider) findTxtRecords(domain, fqdn string) ([]dnsimple.Record, error) {
|
||||
zoneID, zoneName, err := c.getHostedZone(domain)
|
||||
func (c *DNSProvider) findTxtRecords(domain, fqdn string) ([]dnsimple.ZoneRecord, error) {
|
||||
zoneName, err := c.getHostedZone(domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var records []dnsimple.Record
|
||||
result, _, err := c.client.Domains.ListRecords(zoneID, "", "TXT")
|
||||
accountID, err := c.getAccountID()
|
||||
if err != nil {
|
||||
return records, fmt.Errorf("DNSimple API call has failed: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
recordName := c.extractRecordName(fqdn, zoneName)
|
||||
for _, record := range result {
|
||||
if record.Name == recordName {
|
||||
records = append(records, record)
|
||||
}
|
||||
|
||||
result, err := c.client.Zones.ListRecords(accountID, zoneName, &dnsimple.ZoneRecordListOptions{Name: recordName, Type: "TXT", ListOptions: dnsimple.ListOptions{}})
|
||||
if err != nil {
|
||||
return []dnsimple.ZoneRecord{}, fmt.Errorf("DNSimple API call has failed: %v", err)
|
||||
}
|
||||
|
||||
return records, nil
|
||||
return result.Data, nil
|
||||
}
|
||||
|
||||
func (c *DNSProvider) newTxtRecord(zone, fqdn, value string, ttl int) *dnsimple.Record {
|
||||
name := c.extractRecordName(fqdn, zone)
|
||||
func (c *DNSProvider) newTxtRecord(zoneName, fqdn, value string, ttl int) *dnsimple.ZoneRecord {
|
||||
name := c.extractRecordName(fqdn, zoneName)
|
||||
|
||||
return &dnsimple.Record{
|
||||
return &dnsimple.ZoneRecord{
|
||||
Type: "TXT",
|
||||
Name: name,
|
||||
Content: value,
|
||||
|
@ -139,3 +165,16 @@ func (c *DNSProvider) extractRecordName(fqdn, domain string) string {
|
|||
}
|
||||
return name
|
||||
}
|
||||
|
||||
func (c *DNSProvider) getAccountID() (string, error) {
|
||||
whoamiResponse, err := c.client.Identity.Whoami()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if whoamiResponse.Data.Account == nil {
|
||||
return "", fmt.Errorf("DNSimple user tokens are not supported, please use an account token.")
|
||||
}
|
||||
|
||||
return strconv.Itoa(whoamiResponse.Data.Account.ID), nil
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue