Vendor main dependencies.
This commit is contained in:
parent
49a09ab7dd
commit
dd5e3fba01
2738 changed files with 1045689 additions and 0 deletions
141
vendor/github.com/xenolf/lego/providers/dns/auroradns/auroradns.go
generated
vendored
Normal file
141
vendor/github.com/xenolf/lego/providers/dns/auroradns/auroradns.go
generated
vendored
Normal file
|
@ -0,0 +1,141 @@
|
|||
package auroradns
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/edeckers/auroradnsclient"
|
||||
"github.com/edeckers/auroradnsclient/records"
|
||||
"github.com/edeckers/auroradnsclient/zones"
|
||||
"github.com/xenolf/lego/acme"
|
||||
"os"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// DNSProvider describes a provider for AuroraDNS
|
||||
type DNSProvider struct {
|
||||
recordIDs map[string]string
|
||||
recordIDsMu sync.Mutex
|
||||
client *auroradnsclient.AuroraDNSClient
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for AuroraDNS.
|
||||
// Credentials must be passed in the environment variables: AURORA_USER_ID
|
||||
// and AURORA_KEY.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
userID := os.Getenv("AURORA_USER_ID")
|
||||
key := os.Getenv("AURORA_KEY")
|
||||
|
||||
endpoint := os.Getenv("AURORA_ENDPOINT")
|
||||
if endpoint == "" {
|
||||
endpoint = "https://api.auroradns.eu"
|
||||
}
|
||||
|
||||
return NewDNSProviderCredentials(endpoint, userID, key)
|
||||
}
|
||||
|
||||
// NewDNSProviderCredentials uses the supplied credentials to return a
|
||||
// DNSProvider instance configured for AuroraDNS.
|
||||
func NewDNSProviderCredentials(baseURL string, userID string, key string) (*DNSProvider, error) {
|
||||
client, err := auroradnsclient.NewAuroraDNSClient(baseURL, userID, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &DNSProvider{
|
||||
client: client,
|
||||
recordIDs: make(map[string]string),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (provider *DNSProvider) getZoneInformationByName(name string) (zones.ZoneRecord, error) {
|
||||
zs, err := provider.client.GetZones()
|
||||
|
||||
if err != nil {
|
||||
return zones.ZoneRecord{}, err
|
||||
}
|
||||
|
||||
for _, element := range zs {
|
||||
if element.Name == name {
|
||||
return element, nil
|
||||
}
|
||||
}
|
||||
|
||||
return zones.ZoneRecord{}, fmt.Errorf("Could not find Zone record")
|
||||
}
|
||||
|
||||
// Present creates a record with a secret
|
||||
func (provider *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// 1. Aurora will happily create the TXT record when it is provided a fqdn,
|
||||
// but it will only appear in the control panel and will not be
|
||||
// propagated to DNS servers. Extract and use subdomain instead.
|
||||
// 2. A trailing dot in the fqdn will cause Aurora to add a trailing dot to
|
||||
// the subdomain, resulting in _acme-challenge..<domain> rather
|
||||
// than _acme-challenge.<domain>
|
||||
|
||||
subdomain := fqdn[0 : len(fqdn)-len(authZone)-1]
|
||||
|
||||
authZone = acme.UnFqdn(authZone)
|
||||
|
||||
zoneRecord, err := provider.getZoneInformationByName(authZone)
|
||||
|
||||
reqData :=
|
||||
records.CreateRecordRequest{
|
||||
RecordType: "TXT",
|
||||
Name: subdomain,
|
||||
Content: value,
|
||||
TTL: 300,
|
||||
}
|
||||
|
||||
respData, err := provider.client.CreateRecord(zoneRecord.ID, reqData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not create record: '%s'.", err)
|
||||
}
|
||||
|
||||
provider.recordIDsMu.Lock()
|
||||
provider.recordIDs[fqdn] = respData.ID
|
||||
provider.recordIDsMu.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes a given record that was generated by Present
|
||||
func (provider *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
|
||||
|
||||
provider.recordIDsMu.Lock()
|
||||
recordID, ok := provider.recordIDs[fqdn]
|
||||
provider.recordIDsMu.Unlock()
|
||||
|
||||
if !ok {
|
||||
return fmt.Errorf("Unknown recordID for '%s'", fqdn)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
authZone = acme.UnFqdn(authZone)
|
||||
|
||||
zoneRecord, err := provider.getZoneInformationByName(authZone)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = provider.client.RemoveRecord(zoneRecord.ID, recordID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
provider.recordIDsMu.Lock()
|
||||
delete(provider.recordIDs, fqdn)
|
||||
provider.recordIDsMu.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
142
vendor/github.com/xenolf/lego/providers/dns/azure/azure.go
generated
vendored
Normal file
142
vendor/github.com/xenolf/lego/providers/dns/azure/azure.go
generated
vendored
Normal file
|
@ -0,0 +1,142 @@
|
|||
// Package azure implements a DNS provider for solving the DNS-01
|
||||
// challenge using azure DNS.
|
||||
// Azure doesn't like trailing dots on domain names, most of the acme code does.
|
||||
package azure
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/arm/dns"
|
||||
|
||||
"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
|
||||
type DNSProvider struct {
|
||||
clientId string
|
||||
clientSecret string
|
||||
subscriptionId string
|
||||
tenantId string
|
||||
resourceGroup string
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for azure.
|
||||
// Credentials must be passed in the environment variables: AZURE_CLIENT_ID,
|
||||
// AZURE_CLIENT_SECRET, AZURE_SUBSCRIPTION_ID, AZURE_TENANT_ID
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
clientId := os.Getenv("AZURE_CLIENT_ID")
|
||||
clientSecret := os.Getenv("AZURE_CLIENT_SECRET")
|
||||
subscriptionId := os.Getenv("AZURE_SUBSCRIPTION_ID")
|
||||
tenantId := os.Getenv("AZURE_TENANT_ID")
|
||||
resourceGroup := os.Getenv("AZURE_RESOURCE_GROUP")
|
||||
return NewDNSProviderCredentials(clientId, clientSecret, subscriptionId, tenantId, resourceGroup)
|
||||
}
|
||||
|
||||
// NewDNSProviderCredentials uses the supplied credentials to return a
|
||||
// DNSProvider instance configured for azure.
|
||||
func NewDNSProviderCredentials(clientId, clientSecret, subscriptionId, tenantId, resourceGroup string) (*DNSProvider, error) {
|
||||
if clientId == "" || clientSecret == "" || subscriptionId == "" || tenantId == "" || resourceGroup == "" {
|
||||
return nil, fmt.Errorf("Azure configuration missing")
|
||||
}
|
||||
|
||||
return &DNSProvider{
|
||||
clientId: clientId,
|
||||
clientSecret: clientSecret,
|
||||
subscriptionId: subscriptionId,
|
||||
tenantId: tenantId,
|
||||
resourceGroup: resourceGroup,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Timeout returns the timeout and interval to use when checking for DNS
|
||||
// propagation. Adjusting here to cope with spikes in propagation times.
|
||||
func (c *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return 120 * time.Second, 2 * time.Second
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfil the dns-01 challenge
|
||||
func (c *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
|
||||
zone, err := c.getHostedZoneID(fqdn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rsc := dns.NewRecordSetsClient(c.subscriptionId)
|
||||
rsc.Authorizer, err = c.newServicePrincipalTokenFromCredentials(azure.PublicCloud.ResourceManagerEndpoint)
|
||||
relative := toRelativeRecord(fqdn, acme.ToFqdn(zone))
|
||||
rec := dns.RecordSet{
|
||||
Name: &relative,
|
||||
RecordSetProperties: &dns.RecordSetProperties{
|
||||
TTL: to.Int64Ptr(60),
|
||||
TXTRecords: &[]dns.TxtRecord{dns.TxtRecord{Value: &[]string{value}}},
|
||||
},
|
||||
}
|
||||
_, err = rsc.CreateOrUpdate(c.resourceGroup, zone, relative, dns.TXT, rec, "", "")
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Returns the relative record to the domain
|
||||
func toRelativeRecord(domain, zone string) string {
|
||||
return acme.UnFqdn(strings.TrimSuffix(domain, zone))
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters
|
||||
func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
|
||||
|
||||
zone, err := c.getHostedZoneID(fqdn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
relative := toRelativeRecord(fqdn, acme.ToFqdn(zone))
|
||||
rsc := dns.NewRecordSetsClient(c.subscriptionId)
|
||||
rsc.Authorizer, err = c.newServicePrincipalTokenFromCredentials(azure.PublicCloud.ResourceManagerEndpoint)
|
||||
_, err = rsc.Delete(c.resourceGroup, zone, relative, dns.TXT, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Checks that azure has a zone for this domain name.
|
||||
func (c *DNSProvider) getHostedZoneID(fqdn string) (string, error) {
|
||||
authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Now we want to to Azure and get the zone.
|
||||
dc := dns.NewZonesClient(c.subscriptionId)
|
||||
dc.Authorizer, err = c.newServicePrincipalTokenFromCredentials(azure.PublicCloud.ResourceManagerEndpoint)
|
||||
zone, err := dc.Get(c.resourceGroup, acme.UnFqdn(authZone))
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// zone.Name shouldn't have a trailing dot(.)
|
||||
return to.String(zone.Name), nil
|
||||
}
|
||||
|
||||
// NewServicePrincipalTokenFromCredentials creates a new ServicePrincipalToken using values of the
|
||||
// passed credentials map.
|
||||
func (c *DNSProvider) newServicePrincipalTokenFromCredentials(scope string) (*azure.ServicePrincipalToken, error) {
|
||||
oauthConfig, err := azure.PublicCloud.OAuthConfigForTenant(c.tenantId)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return azure.NewServicePrincipalToken(*oauthConfig, c.clientId, c.clientSecret, scope)
|
||||
}
|
223
vendor/github.com/xenolf/lego/providers/dns/cloudflare/cloudflare.go
generated
vendored
Normal file
223
vendor/github.com/xenolf/lego/providers/dns/cloudflare/cloudflare.go
generated
vendored
Normal file
|
@ -0,0 +1,223 @@
|
|||
// Package cloudflare implements a DNS provider for solving the DNS-01
|
||||
// challenge using cloudflare DNS.
|
||||
package cloudflare
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/xenolf/lego/acme"
|
||||
)
|
||||
|
||||
// CloudFlareAPIURL represents the API endpoint to call.
|
||||
// TODO: Unexport?
|
||||
const CloudFlareAPIURL = "https://api.cloudflare.com/client/v4"
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface
|
||||
type DNSProvider struct {
|
||||
authEmail string
|
||||
authKey string
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for cloudflare.
|
||||
// Credentials must be passed in the environment variables: CLOUDFLARE_EMAIL
|
||||
// and CLOUDFLARE_API_KEY.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
email := os.Getenv("CLOUDFLARE_EMAIL")
|
||||
key := os.Getenv("CLOUDFLARE_API_KEY")
|
||||
return NewDNSProviderCredentials(email, key)
|
||||
}
|
||||
|
||||
// NewDNSProviderCredentials uses the supplied credentials to return a
|
||||
// DNSProvider instance configured for cloudflare.
|
||||
func NewDNSProviderCredentials(email, key string) (*DNSProvider, error) {
|
||||
if email == "" || key == "" {
|
||||
return nil, fmt.Errorf("CloudFlare credentials missing")
|
||||
}
|
||||
|
||||
return &DNSProvider{
|
||||
authEmail: email,
|
||||
authKey: key,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Timeout returns the timeout and interval to use when checking for DNS
|
||||
// propagation. Adjusting here to cope with spikes in propagation times.
|
||||
func (c *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return 120 * time.Second, 2 * time.Second
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfil the dns-01 challenge
|
||||
func (c *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
|
||||
zoneID, err := c.getHostedZoneID(fqdn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rec := cloudFlareRecord{
|
||||
Type: "TXT",
|
||||
Name: acme.UnFqdn(fqdn),
|
||||
Content: value,
|
||||
TTL: 120,
|
||||
}
|
||||
|
||||
body, err := json.Marshal(rec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = c.makeRequest("POST", fmt.Sprintf("/zones/%s/dns_records", zoneID), bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters
|
||||
func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
|
||||
|
||||
record, err := c.findTxtRecord(fqdn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = c.makeRequest("DELETE", fmt.Sprintf("/zones/%s/dns_records/%s", record.ZoneID, record.ID), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *DNSProvider) getHostedZoneID(fqdn string) (string, error) {
|
||||
// HostedZone represents a CloudFlare DNS zone
|
||||
type HostedZone struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
result, err := c.makeRequest("GET", "/zones?name="+acme.UnFqdn(authZone), nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var hostedZone []HostedZone
|
||||
err = json.Unmarshal(result, &hostedZone)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(hostedZone) != 1 {
|
||||
return "", fmt.Errorf("Zone %s not found in CloudFlare for domain %s", authZone, fqdn)
|
||||
}
|
||||
|
||||
return hostedZone[0].ID, nil
|
||||
}
|
||||
|
||||
func (c *DNSProvider) findTxtRecord(fqdn string) (*cloudFlareRecord, error) {
|
||||
zoneID, err := c.getHostedZoneID(fqdn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result, err := c.makeRequest(
|
||||
"GET",
|
||||
fmt.Sprintf("/zones/%s/dns_records?per_page=1000&type=TXT&name=%s", zoneID, acme.UnFqdn(fqdn)),
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var records []cloudFlareRecord
|
||||
err = json.Unmarshal(result, &records)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, rec := range records {
|
||||
if rec.Name == acme.UnFqdn(fqdn) {
|
||||
return &rec, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("No existing record found for %s", fqdn)
|
||||
}
|
||||
|
||||
func (c *DNSProvider) makeRequest(method, uri string, body io.Reader) (json.RawMessage, error) {
|
||||
// APIError contains error details for failed requests
|
||||
type APIError struct {
|
||||
Code int `json:"code,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
ErrorChain []APIError `json:"error_chain,omitempty"`
|
||||
}
|
||||
|
||||
// APIResponse represents a response from CloudFlare API
|
||||
type APIResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Errors []*APIError `json:"errors"`
|
||||
Result json.RawMessage `json:"result"`
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, fmt.Sprintf("%s%s", CloudFlareAPIURL, uri), body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("X-Auth-Email", c.authEmail)
|
||||
req.Header.Set("X-Auth-Key", c.authKey)
|
||||
//req.Header.Set("User-Agent", userAgent())
|
||||
|
||||
client := http.Client{Timeout: 30 * time.Second}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error querying Cloudflare API -> %v", err)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
var r APIResponse
|
||||
err = json.NewDecoder(resp.Body).Decode(&r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !r.Success {
|
||||
if len(r.Errors) > 0 {
|
||||
errStr := ""
|
||||
for _, apiErr := range r.Errors {
|
||||
errStr += fmt.Sprintf("\t Error: %d: %s", apiErr.Code, apiErr.Message)
|
||||
for _, chainErr := range apiErr.ErrorChain {
|
||||
errStr += fmt.Sprintf("<- %d: %s", chainErr.Code, chainErr.Message)
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("Cloudflare API Error \n%s", errStr)
|
||||
}
|
||||
return nil, fmt.Errorf("Cloudflare API error")
|
||||
}
|
||||
|
||||
return r.Result, nil
|
||||
}
|
||||
|
||||
// cloudFlareRecord represents a CloudFlare DNS record
|
||||
type cloudFlareRecord struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Content string `json:"content"`
|
||||
ID string `json:"id,omitempty"`
|
||||
TTL int `json:"ttl,omitempty"`
|
||||
ZoneID string `json:"zone_id,omitempty"`
|
||||
}
|
166
vendor/github.com/xenolf/lego/providers/dns/digitalocean/digitalocean.go
generated
vendored
Normal file
166
vendor/github.com/xenolf/lego/providers/dns/digitalocean/digitalocean.go
generated
vendored
Normal file
|
@ -0,0 +1,166 @@
|
|||
// Package digitalocean implements a DNS provider for solving the DNS-01
|
||||
// challenge using digitalocean DNS.
|
||||
package digitalocean
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/xenolf/lego/acme"
|
||||
)
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface
|
||||
// that uses DigitalOcean's REST API to manage TXT records for a domain.
|
||||
type DNSProvider struct {
|
||||
apiAuthToken string
|
||||
recordIDs map[string]int
|
||||
recordIDsMu sync.Mutex
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for Digital
|
||||
// Ocean. Credentials must be passed in the environment variable:
|
||||
// DO_AUTH_TOKEN.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
apiAuthToken := os.Getenv("DO_AUTH_TOKEN")
|
||||
return NewDNSProviderCredentials(apiAuthToken)
|
||||
}
|
||||
|
||||
// NewDNSProviderCredentials uses the supplied credentials to return a
|
||||
// DNSProvider instance configured for Digital Ocean.
|
||||
func NewDNSProviderCredentials(apiAuthToken string) (*DNSProvider, error) {
|
||||
if apiAuthToken == "" {
|
||||
return nil, fmt.Errorf("DigitalOcean credentials missing")
|
||||
}
|
||||
return &DNSProvider{
|
||||
apiAuthToken: apiAuthToken,
|
||||
recordIDs: make(map[string]int),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Present creates a TXT record using the specified parameters
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
// 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"`
|
||||
}
|
||||
|
||||
// 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"`
|
||||
}
|
||||
|
||||
fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
authZone = acme.UnFqdn(authZone)
|
||||
|
||||
reqURL := fmt.Sprintf("%s/v2/domains/%s/records", digitalOceanBaseURL, authZone)
|
||||
reqData := txtRecordRequest{RecordType: "TXT", Name: fqdn, Data: value}
|
||||
body, err := json.Marshal(reqData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", reqURL, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", d.apiAuthToken))
|
||||
|
||||
client := http.Client{Timeout: 30 * time.Second}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode >= 400 {
|
||||
var errInfo digitalOceanAPIError
|
||||
json.NewDecoder(resp.Body).Decode(&errInfo)
|
||||
return fmt.Errorf("HTTP %d: %s: %s", resp.StatusCode, errInfo.ID, errInfo.Message)
|
||||
}
|
||||
|
||||
// Everything looks good; but we'll need the ID later to delete the record
|
||||
var respData txtRecordResponse
|
||||
err = json.NewDecoder(resp.Body).Decode(&respData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.recordIDsMu.Lock()
|
||||
d.recordIDs[fqdn] = respData.DomainRecord.ID
|
||||
d.recordIDsMu.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
|
||||
|
||||
// get the record's unique ID from when we created it
|
||||
d.recordIDsMu.Lock()
|
||||
recordID, ok := d.recordIDs[fqdn]
|
||||
d.recordIDsMu.Unlock()
|
||||
if !ok {
|
||||
return fmt.Errorf("unknown record ID for '%s'", fqdn)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
authZone = acme.UnFqdn(authZone)
|
||||
|
||||
reqURL := fmt.Sprintf("%s/v2/domains/%s/records/%d", digitalOceanBaseURL, authZone, recordID)
|
||||
req, err := http.NewRequest("DELETE", reqURL, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", d.apiAuthToken))
|
||||
|
||||
client := http.Client{Timeout: 30 * time.Second}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode >= 400 {
|
||||
var errInfo digitalOceanAPIError
|
||||
json.NewDecoder(resp.Body).Decode(&errInfo)
|
||||
return fmt.Errorf("HTTP %d: %s: %s", resp.StatusCode, errInfo.ID, errInfo.Message)
|
||||
}
|
||||
|
||||
// Delete record ID from map
|
||||
d.recordIDsMu.Lock()
|
||||
delete(d.recordIDs, fqdn)
|
||||
d.recordIDsMu.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type digitalOceanAPIError struct {
|
||||
ID string `json:"id"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
var digitalOceanBaseURL = "https://api.digitalocean.com"
|
80
vendor/github.com/xenolf/lego/providers/dns/dns_providers.go
generated
vendored
Normal file
80
vendor/github.com/xenolf/lego/providers/dns/dns_providers.go
generated
vendored
Normal file
|
@ -0,0 +1,80 @@
|
|||
// Factory for DNS providers
|
||||
package dns
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/xenolf/lego/acme"
|
||||
"github.com/xenolf/lego/providers/dns/auroradns"
|
||||
"github.com/xenolf/lego/providers/dns/azure"
|
||||
"github.com/xenolf/lego/providers/dns/cloudflare"
|
||||
"github.com/xenolf/lego/providers/dns/digitalocean"
|
||||
"github.com/xenolf/lego/providers/dns/dnsimple"
|
||||
"github.com/xenolf/lego/providers/dns/dnsmadeeasy"
|
||||
"github.com/xenolf/lego/providers/dns/dnspod"
|
||||
"github.com/xenolf/lego/providers/dns/dyn"
|
||||
"github.com/xenolf/lego/providers/dns/exoscale"
|
||||
"github.com/xenolf/lego/providers/dns/gandi"
|
||||
"github.com/xenolf/lego/providers/dns/googlecloud"
|
||||
"github.com/xenolf/lego/providers/dns/linode"
|
||||
"github.com/xenolf/lego/providers/dns/namecheap"
|
||||
"github.com/xenolf/lego/providers/dns/ns1"
|
||||
"github.com/xenolf/lego/providers/dns/ovh"
|
||||
"github.com/xenolf/lego/providers/dns/pdns"
|
||||
"github.com/xenolf/lego/providers/dns/rackspace"
|
||||
"github.com/xenolf/lego/providers/dns/rfc2136"
|
||||
"github.com/xenolf/lego/providers/dns/route53"
|
||||
"github.com/xenolf/lego/providers/dns/vultr"
|
||||
)
|
||||
|
||||
func NewDNSChallengeProviderByName(name string) (acme.ChallengeProvider, error) {
|
||||
var err error
|
||||
var provider acme.ChallengeProvider
|
||||
switch name {
|
||||
case "azure":
|
||||
provider, err = azure.NewDNSProvider()
|
||||
case "auroradns":
|
||||
provider, err = auroradns.NewDNSProvider()
|
||||
case "cloudflare":
|
||||
provider, err = cloudflare.NewDNSProvider()
|
||||
case "digitalocean":
|
||||
provider, err = digitalocean.NewDNSProvider()
|
||||
case "dnsimple":
|
||||
provider, err = dnsimple.NewDNSProvider()
|
||||
case "dnsmadeeasy":
|
||||
provider, err = dnsmadeeasy.NewDNSProvider()
|
||||
case "dnspod":
|
||||
provider, err = dnspod.NewDNSProvider()
|
||||
case "dyn":
|
||||
provider, err = dyn.NewDNSProvider()
|
||||
case "exoscale":
|
||||
provider, err = exoscale.NewDNSProvider()
|
||||
case "gandi":
|
||||
provider, err = gandi.NewDNSProvider()
|
||||
case "gcloud":
|
||||
provider, err = googlecloud.NewDNSProvider()
|
||||
case "linode":
|
||||
provider, err = linode.NewDNSProvider()
|
||||
case "manual":
|
||||
provider, err = acme.NewDNSProviderManual()
|
||||
case "namecheap":
|
||||
provider, err = namecheap.NewDNSProvider()
|
||||
case "rackspace":
|
||||
provider, err = rackspace.NewDNSProvider()
|
||||
case "route53":
|
||||
provider, err = route53.NewDNSProvider()
|
||||
case "rfc2136":
|
||||
provider, err = rfc2136.NewDNSProvider()
|
||||
case "vultr":
|
||||
provider, err = vultr.NewDNSProvider()
|
||||
case "ovh":
|
||||
provider, err = ovh.NewDNSProvider()
|
||||
case "pdns":
|
||||
provider, err = pdns.NewDNSProvider()
|
||||
case "ns1":
|
||||
provider, err = ns1.NewDNSProvider()
|
||||
default:
|
||||
err = fmt.Errorf("Unrecognised DNS provider: %s", name)
|
||||
}
|
||||
return provider, err
|
||||
}
|
141
vendor/github.com/xenolf/lego/providers/dns/dnsimple/dnsimple.go
generated
vendored
Normal file
141
vendor/github.com/xenolf/lego/providers/dns/dnsimple/dnsimple.go
generated
vendored
Normal file
|
@ -0,0 +1,141 @@
|
|||
// Package dnsimple implements a DNS provider for solving the DNS-01 challenge
|
||||
// using dnsimple DNS.
|
||||
package dnsimple
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/weppos/dnsimple-go/dnsimple"
|
||||
"github.com/xenolf/lego/acme"
|
||||
)
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface.
|
||||
type DNSProvider struct {
|
||||
client *dnsimple.Client
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for dnsimple.
|
||||
// Credentials must be passed in the environment variables: DNSIMPLE_EMAIL
|
||||
// and DNSIMPLE_API_KEY.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
email := os.Getenv("DNSIMPLE_EMAIL")
|
||||
key := os.Getenv("DNSIMPLE_API_KEY")
|
||||
return NewDNSProviderCredentials(email, key)
|
||||
}
|
||||
|
||||
// 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")
|
||||
}
|
||||
|
||||
return &DNSProvider{
|
||||
client: dnsimple.NewClient(key, email),
|
||||
}, 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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
recordAttributes := c.newTxtRecord(zoneName, fqdn, value, ttl)
|
||||
_, _, err = c.client.Domains.CreateRecord(zoneID, *recordAttributes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("DNSimple API call failed: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters.
|
||||
func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
|
||||
|
||||
records, err := c.findTxtRecords(domain, fqdn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, rec := range records {
|
||||
_, err := c.client.Domains.DeleteRecord(rec.DomainId, 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)
|
||||
}
|
||||
|
||||
authZone, err := acme.FindZoneByFqdn(acme.ToFqdn(domain), acme.RecursiveNameservers)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
var hostedZone dnsimple.Domain
|
||||
for _, zone := range zones {
|
||||
if zone.Name == acme.UnFqdn(authZone) {
|
||||
hostedZone = zone
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func (c *DNSProvider) findTxtRecords(domain, fqdn string) ([]dnsimple.Record, error) {
|
||||
zoneID, zoneName, err := c.getHostedZone(domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var records []dnsimple.Record
|
||||
result, _, err := c.client.Domains.ListRecords(zoneID, "", "TXT")
|
||||
if err != nil {
|
||||
return records, fmt.Errorf("DNSimple API call has failed: %v", err)
|
||||
}
|
||||
|
||||
recordName := c.extractRecordName(fqdn, zoneName)
|
||||
for _, record := range result {
|
||||
if record.Name == recordName {
|
||||
records = append(records, record)
|
||||
}
|
||||
}
|
||||
|
||||
return records, nil
|
||||
}
|
||||
|
||||
func (c *DNSProvider) newTxtRecord(zone, fqdn, value string, ttl int) *dnsimple.Record {
|
||||
name := c.extractRecordName(fqdn, zone)
|
||||
|
||||
return &dnsimple.Record{
|
||||
Type: "TXT",
|
||||
Name: name,
|
||||
Content: value,
|
||||
TTL: ttl,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *DNSProvider) extractRecordName(fqdn, domain string) string {
|
||||
name := acme.UnFqdn(fqdn)
|
||||
if idx := strings.Index(name, "."+domain); idx != -1 {
|
||||
return name[:idx]
|
||||
}
|
||||
return name
|
||||
}
|
248
vendor/github.com/xenolf/lego/providers/dns/dnsmadeeasy/dnsmadeeasy.go
generated
vendored
Normal file
248
vendor/github.com/xenolf/lego/providers/dns/dnsmadeeasy/dnsmadeeasy.go
generated
vendored
Normal file
|
@ -0,0 +1,248 @@
|
|||
package dnsmadeeasy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"crypto/tls"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/xenolf/lego/acme"
|
||||
)
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface that uses
|
||||
// DNSMadeEasy's DNS API to manage TXT records for a domain.
|
||||
type DNSProvider struct {
|
||||
baseURL string
|
||||
apiKey string
|
||||
apiSecret string
|
||||
}
|
||||
|
||||
// Domain holds the DNSMadeEasy API representation of a Domain
|
||||
type Domain struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// Record holds the DNSMadeEasy API representation of a Domain Record
|
||||
type Record struct {
|
||||
ID int `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
TTL int `json:"ttl"`
|
||||
SourceID int `json:"sourceId"`
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for DNSMadeEasy DNS.
|
||||
// Credentials must be passed in the environment variables: DNSMADEEASY_API_KEY
|
||||
// and DNSMADEEASY_API_SECRET.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
dnsmadeeasyAPIKey := os.Getenv("DNSMADEEASY_API_KEY")
|
||||
dnsmadeeasyAPISecret := os.Getenv("DNSMADEEASY_API_SECRET")
|
||||
dnsmadeeasySandbox := os.Getenv("DNSMADEEASY_SANDBOX")
|
||||
|
||||
var baseURL string
|
||||
|
||||
sandbox, _ := strconv.ParseBool(dnsmadeeasySandbox)
|
||||
if sandbox {
|
||||
baseURL = "https://api.sandbox.dnsmadeeasy.com/V2.0"
|
||||
} else {
|
||||
baseURL = "https://api.dnsmadeeasy.com/V2.0"
|
||||
}
|
||||
|
||||
return NewDNSProviderCredentials(baseURL, dnsmadeeasyAPIKey, dnsmadeeasyAPISecret)
|
||||
}
|
||||
|
||||
// NewDNSProviderCredentials uses the supplied credentials to return a
|
||||
// DNSProvider instance configured for DNSMadeEasy.
|
||||
func NewDNSProviderCredentials(baseURL, apiKey, apiSecret string) (*DNSProvider, error) {
|
||||
if baseURL == "" || apiKey == "" || apiSecret == "" {
|
||||
return nil, fmt.Errorf("DNS Made Easy credentials missing")
|
||||
}
|
||||
|
||||
return &DNSProvider{
|
||||
baseURL: baseURL,
|
||||
apiKey: apiKey,
|
||||
apiSecret: apiSecret,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Present creates a TXT record using the specified parameters
|
||||
func (d *DNSProvider) Present(domainName, token, keyAuth string) error {
|
||||
fqdn, value, ttl := acme.DNS01Record(domainName, keyAuth)
|
||||
|
||||
authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// fetch the domain details
|
||||
domain, err := d.getDomain(authZone)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// create the TXT record
|
||||
name := strings.Replace(fqdn, "."+authZone, "", 1)
|
||||
record := &Record{Type: "TXT", Name: name, Value: value, TTL: ttl}
|
||||
|
||||
err = d.createRecord(domain, record)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT records matching the specified parameters
|
||||
func (d *DNSProvider) CleanUp(domainName, token, keyAuth string) error {
|
||||
fqdn, _, _ := acme.DNS01Record(domainName, keyAuth)
|
||||
|
||||
authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// fetch the domain details
|
||||
domain, err := d.getDomain(authZone)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// find matching records
|
||||
name := strings.Replace(fqdn, "."+authZone, "", 1)
|
||||
records, err := d.getRecords(domain, name, "TXT")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// delete records
|
||||
for _, record := range *records {
|
||||
err = d.deleteRecord(record)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) getDomain(authZone string) (*Domain, error) {
|
||||
domainName := authZone[0 : len(authZone)-1]
|
||||
resource := fmt.Sprintf("%s%s", "/dns/managed/name?domainname=", domainName)
|
||||
|
||||
resp, err := d.sendRequest("GET", resource, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
domain := &Domain{}
|
||||
err = json.NewDecoder(resp.Body).Decode(&domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return domain, nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) getRecords(domain *Domain, recordName, recordType string) (*[]Record, error) {
|
||||
resource := fmt.Sprintf("%s/%d/%s%s%s%s", "/dns/managed", domain.ID, "records?recordName=", recordName, "&type=", recordType)
|
||||
|
||||
resp, err := d.sendRequest("GET", resource, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
type recordsResponse struct {
|
||||
Records *[]Record `json:"data"`
|
||||
}
|
||||
|
||||
records := &recordsResponse{}
|
||||
err = json.NewDecoder(resp.Body).Decode(&records)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return records.Records, nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) createRecord(domain *Domain, record *Record) error {
|
||||
url := fmt.Sprintf("%s/%d/%s", "/dns/managed", domain.ID, "records")
|
||||
|
||||
resp, err := d.sendRequest("POST", url, record)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) deleteRecord(record Record) error {
|
||||
resource := fmt.Sprintf("%s/%d/%s/%d", "/dns/managed", record.SourceID, "records", record.ID)
|
||||
|
||||
resp, err := d.sendRequest("DELETE", resource, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) sendRequest(method, resource string, payload interface{}) (*http.Response, error) {
|
||||
url := fmt.Sprintf("%s%s", d.baseURL, resource)
|
||||
|
||||
body, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
timestamp := time.Now().UTC().Format(time.RFC1123)
|
||||
signature := computeHMAC(timestamp, d.apiSecret)
|
||||
|
||||
req, err := http.NewRequest(method, url, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("x-dnsme-apiKey", d.apiKey)
|
||||
req.Header.Set("x-dnsme-requestDate", timestamp)
|
||||
req.Header.Set("x-dnsme-hmac", signature)
|
||||
req.Header.Set("accept", "application/json")
|
||||
req.Header.Set("content-type", "application/json")
|
||||
|
||||
transport := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
client := &http.Client{
|
||||
Transport: transport,
|
||||
Timeout: time.Duration(10 * time.Second),
|
||||
}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode > 299 {
|
||||
return nil, fmt.Errorf("DNSMadeEasy API request failed with HTTP status code %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func computeHMAC(message string, secret string) string {
|
||||
key := []byte(secret)
|
||||
h := hmac.New(sha1.New, key)
|
||||
h.Write([]byte(message))
|
||||
return hex.EncodeToString(h.Sum(nil))
|
||||
}
|
146
vendor/github.com/xenolf/lego/providers/dns/dnspod/dnspod.go
generated
vendored
Normal file
146
vendor/github.com/xenolf/lego/providers/dns/dnspod/dnspod.go
generated
vendored
Normal file
|
@ -0,0 +1,146 @@
|
|||
// Package dnspod implements a DNS provider for solving the DNS-01 challenge
|
||||
// using dnspod DNS.
|
||||
package dnspod
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/decker502/dnspod-go"
|
||||
"github.com/xenolf/lego/acme"
|
||||
)
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface.
|
||||
type DNSProvider struct {
|
||||
client *dnspod.Client
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for dnspod.
|
||||
// Credentials must be passed in the environment variables: DNSPOD_API_KEY.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
key := os.Getenv("DNSPOD_API_KEY")
|
||||
return NewDNSProviderCredentials(key)
|
||||
}
|
||||
|
||||
// NewDNSProviderCredentials uses the supplied credentials to return a
|
||||
// DNSProvider instance configured for dnspod.
|
||||
func NewDNSProviderCredentials(key string) (*DNSProvider, error) {
|
||||
if key == "" {
|
||||
return nil, fmt.Errorf("dnspod credentials missing")
|
||||
}
|
||||
|
||||
params := dnspod.CommonParams{LoginToken: key, Format: "json"}
|
||||
return &DNSProvider{
|
||||
client: dnspod.NewClient(params),
|
||||
}, 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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
recordAttributes := c.newTxtRecord(zoneName, fqdn, value, ttl)
|
||||
_, _, err = c.client.Domains.CreateRecord(zoneID, *recordAttributes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dnspod API call failed: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters.
|
||||
func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
|
||||
|
||||
records, err := c.findTxtRecords(domain, fqdn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
zoneID, _, err := c.getHostedZone(domain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, rec := range records {
|
||||
_, err := c.client.Domains.DeleteRecord(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("dnspod API call failed: %v", err)
|
||||
}
|
||||
|
||||
authZone, err := acme.FindZoneByFqdn(acme.ToFqdn(domain), acme.RecursiveNameservers)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
var hostedZone dnspod.Domain
|
||||
for _, zone := range zones {
|
||||
if zone.Name == acme.UnFqdn(authZone) {
|
||||
hostedZone = zone
|
||||
}
|
||||
}
|
||||
|
||||
if hostedZone.ID == 0 {
|
||||
return "", "", fmt.Errorf("Zone %s not found in dnspod for domain %s", authZone, domain)
|
||||
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%v", hostedZone.ID), hostedZone.Name, nil
|
||||
}
|
||||
|
||||
func (c *DNSProvider) newTxtRecord(zone, fqdn, value string, ttl int) *dnspod.Record {
|
||||
name := c.extractRecordName(fqdn, zone)
|
||||
|
||||
return &dnspod.Record{
|
||||
Type: "TXT",
|
||||
Name: name,
|
||||
Value: value,
|
||||
Line: "默认",
|
||||
TTL: "600",
|
||||
}
|
||||
}
|
||||
|
||||
func (c *DNSProvider) findTxtRecords(domain, fqdn string) ([]dnspod.Record, error) {
|
||||
zoneID, zoneName, err := c.getHostedZone(domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var records []dnspod.Record
|
||||
result, _, err := c.client.Domains.ListRecords(zoneID, "")
|
||||
if err != nil {
|
||||
return records, fmt.Errorf("dnspod API call has failed: %v", err)
|
||||
}
|
||||
|
||||
recordName := c.extractRecordName(fqdn, zoneName)
|
||||
|
||||
for _, record := range result {
|
||||
if record.Name == recordName {
|
||||
records = append(records, record)
|
||||
}
|
||||
}
|
||||
|
||||
return records, nil
|
||||
}
|
||||
|
||||
func (c *DNSProvider) extractRecordName(fqdn, domain string) string {
|
||||
name := acme.UnFqdn(fqdn)
|
||||
if idx := strings.Index(name, "."+domain); idx != -1 {
|
||||
return name[:idx]
|
||||
}
|
||||
return name
|
||||
}
|
274
vendor/github.com/xenolf/lego/providers/dns/dyn/dyn.go
generated
vendored
Normal file
274
vendor/github.com/xenolf/lego/providers/dns/dyn/dyn.go
generated
vendored
Normal file
|
@ -0,0 +1,274 @@
|
|||
// Package dyn implements a DNS provider for solving the DNS-01 challenge
|
||||
// using Dyn Managed DNS.
|
||||
package dyn
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/xenolf/lego/acme"
|
||||
)
|
||||
|
||||
var dynBaseURL = "https://api.dynect.net/REST"
|
||||
|
||||
type dynResponse struct {
|
||||
// One of 'success', 'failure', or 'incomplete'
|
||||
Status string `json:"status"`
|
||||
|
||||
// The structure containing the actual results of the request
|
||||
Data json.RawMessage `json:"data"`
|
||||
|
||||
// The ID of the job that was created in response to a request.
|
||||
JobID int `json:"job_id"`
|
||||
|
||||
// A list of zero or more messages
|
||||
Messages json.RawMessage `json:"msgs"`
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface that uses
|
||||
// Dyn's Managed DNS API to manage TXT records for a domain.
|
||||
type DNSProvider struct {
|
||||
customerName string
|
||||
userName string
|
||||
password string
|
||||
token string
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for Dyn DNS.
|
||||
// Credentials must be passed in the environment variables: DYN_CUSTOMER_NAME,
|
||||
// DYN_USER_NAME and DYN_PASSWORD.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
customerName := os.Getenv("DYN_CUSTOMER_NAME")
|
||||
userName := os.Getenv("DYN_USER_NAME")
|
||||
password := os.Getenv("DYN_PASSWORD")
|
||||
return NewDNSProviderCredentials(customerName, userName, password)
|
||||
}
|
||||
|
||||
// NewDNSProviderCredentials uses the supplied credentials to return a
|
||||
// DNSProvider instance configured for Dyn DNS.
|
||||
func NewDNSProviderCredentials(customerName, userName, password string) (*DNSProvider, error) {
|
||||
if customerName == "" || userName == "" || password == "" {
|
||||
return nil, fmt.Errorf("DynDNS credentials missing")
|
||||
}
|
||||
|
||||
return &DNSProvider{
|
||||
customerName: customerName,
|
||||
userName: userName,
|
||||
password: password,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) sendRequest(method, resource string, payload interface{}) (*dynResponse, error) {
|
||||
url := fmt.Sprintf("%s/%s", dynBaseURL, 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)
|
||||
}
|
||||
|
||||
client := &http.Client{Timeout: time.Duration(10 * time.Second)}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode >= 400 {
|
||||
return nil, fmt.Errorf("Dyn API request failed with HTTP status code %d", resp.StatusCode)
|
||||
} else if resp.StatusCode == 307 {
|
||||
// TODO add support for HTTP 307 response and long running jobs
|
||||
return nil, fmt.Errorf("Dyn API request returned HTTP 307. This is currently unsupported")
|
||||
}
|
||||
|
||||
var dynRes dynResponse
|
||||
err = json.NewDecoder(resp.Body).Decode(&dynRes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if dynRes.Status == "failure" {
|
||||
// TODO add better error handling
|
||||
return nil, fmt.Errorf("Dyn API request failed: %s", dynRes.Messages)
|
||||
}
|
||||
|
||||
return &dynRes, nil
|
||||
}
|
||||
|
||||
// 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 {
|
||||
type creds struct {
|
||||
Customer string `json:"customer_name"`
|
||||
User string `json:"user_name"`
|
||||
Pass string `json:"password"`
|
||||
}
|
||||
|
||||
type session struct {
|
||||
Token string `json:"token"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
payload := &creds{Customer: d.customerName, User: d.userName, Pass: d.password}
|
||||
dynRes, err := d.sendRequest("POST", "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", dynBaseURL)
|
||||
req, err := http.NewRequest("DELETE", url, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Auth-Token", d.token)
|
||||
|
||||
client := &http.Client{Timeout: time.Duration(10 * time.Second)}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return fmt.Errorf("Dyn API request failed to delete session with HTTP status code %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
d.token = ""
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Present creates a TXT record using the specified parameters
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value, ttl := acme.DNS01Record(domain, keyAuth)
|
||||
|
||||
authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = d.login()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data := map[string]interface{}{
|
||||
"rdata": map[string]string{
|
||||
"txtdata": value,
|
||||
},
|
||||
"ttl": strconv.Itoa(ttl),
|
||||
}
|
||||
|
||||
resource := fmt.Sprintf("TXTRecord/%s/%s/", authZone, fqdn)
|
||||
_, err = d.sendRequest("POST", resource, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = d.publish(authZone, "Added TXT record for ACME dns-01 challenge using lego client")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = d.logout()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) publish(zone, notes string) error {
|
||||
type publish struct {
|
||||
Publish bool `json:"publish"`
|
||||
Notes string `json:"notes"`
|
||||
}
|
||||
|
||||
pub := &publish{Publish: true, Notes: notes}
|
||||
resource := fmt.Sprintf("Zone/%s/", zone)
|
||||
_, err := d.sendRequest("PUT", resource, pub)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
|
||||
|
||||
authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = d.login()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resource := fmt.Sprintf("TXTRecord/%s/%s/", authZone, fqdn)
|
||||
url := fmt.Sprintf("%s/%s", dynBaseURL, resource)
|
||||
req, err := http.NewRequest("DELETE", url, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Auth-Token", d.token)
|
||||
|
||||
client := &http.Client{Timeout: time.Duration(10 * time.Second)}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return fmt.Errorf("Dyn API request failed to delete TXT record HTTP status code %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
err = d.publish(authZone, "Removed TXT record for ACME dns-01 challenge using lego client")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = d.logout()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
132
vendor/github.com/xenolf/lego/providers/dns/exoscale/exoscale.go
generated
vendored
Normal file
132
vendor/github.com/xenolf/lego/providers/dns/exoscale/exoscale.go
generated
vendored
Normal file
|
@ -0,0 +1,132 @@
|
|||
// Package exoscale implements a DNS provider for solving the DNS-01 challenge
|
||||
// using exoscale DNS.
|
||||
package exoscale
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/pyr/egoscale/src/egoscale"
|
||||
"github.com/xenolf/lego/acme"
|
||||
)
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface.
|
||||
type DNSProvider struct {
|
||||
client *egoscale.Client
|
||||
}
|
||||
|
||||
// Credentials must be passed in the environment variables:
|
||||
// EXOSCALE_API_KEY, EXOSCALE_API_SECRET, EXOSCALE_ENDPOINT.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
key := os.Getenv("EXOSCALE_API_KEY")
|
||||
secret := os.Getenv("EXOSCALE_API_SECRET")
|
||||
endpoint := os.Getenv("EXOSCALE_ENDPOINT")
|
||||
return NewDNSProviderClient(key, secret, endpoint)
|
||||
}
|
||||
|
||||
// Uses the supplied parameters to return a DNSProvider instance
|
||||
// configured for Exoscale.
|
||||
func NewDNSProviderClient(key, secret, endpoint string) (*DNSProvider, error) {
|
||||
if key == "" || secret == "" {
|
||||
return nil, fmt.Errorf("Exoscale credentials missing")
|
||||
}
|
||||
if endpoint == "" {
|
||||
endpoint = "https://api.exoscale.ch/dns"
|
||||
}
|
||||
|
||||
return &DNSProvider{
|
||||
client: egoscale.NewClient(endpoint, key, secret),
|
||||
}, 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)
|
||||
zone, recordName, err := c.FindZoneAndRecordName(fqdn, domain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
recordId, err := c.FindExistingRecordId(zone, recordName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
record := egoscale.DNSRecord{
|
||||
Name: recordName,
|
||||
Ttl: ttl,
|
||||
Content: value,
|
||||
RecordType: "TXT",
|
||||
}
|
||||
|
||||
if recordId == 0 {
|
||||
_, err := c.client.CreateRecord(zone, record)
|
||||
if err != nil {
|
||||
return errors.New("Error while creating DNS record: " + err.Error())
|
||||
}
|
||||
} else {
|
||||
record.Id = recordId
|
||||
_, err := c.client.UpdateRecord(zone, record)
|
||||
if err != nil {
|
||||
return errors.New("Error while updating DNS record: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the record matching the specified parameters.
|
||||
func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
|
||||
zone, recordName, err := c.FindZoneAndRecordName(fqdn, domain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
recordId, err := c.FindExistingRecordId(zone, recordName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if recordId != 0 {
|
||||
record := egoscale.DNSRecord{
|
||||
Id: recordId,
|
||||
}
|
||||
|
||||
err = c.client.DeleteRecord(zone, record)
|
||||
if err != nil {
|
||||
return errors.New("Error while deleting DNS record: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Query Exoscale to find an existing record for this name.
|
||||
// Returns nil if no record could be found
|
||||
func (c *DNSProvider) FindExistingRecordId(zone, recordName string) (int64, error) {
|
||||
responses, err := c.client.GetRecords(zone)
|
||||
if err != nil {
|
||||
return -1, errors.New("Error while retrievening DNS records: " + err.Error())
|
||||
}
|
||||
for _, response := range responses {
|
||||
if response.Record.Name == recordName {
|
||||
return response.Record.Id, nil
|
||||
}
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// Extract DNS zone and DNS entry name
|
||||
func (c *DNSProvider) FindZoneAndRecordName(fqdn, domain string) (string, string, error) {
|
||||
zone, err := acme.FindZoneByFqdn(acme.ToFqdn(domain), acme.RecursiveNameservers)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
zone = acme.UnFqdn(zone)
|
||||
name := acme.UnFqdn(fqdn)
|
||||
name = name[:len(name)-len("."+zone)]
|
||||
|
||||
return zone, name, nil
|
||||
}
|
472
vendor/github.com/xenolf/lego/providers/dns/gandi/gandi.go
generated
vendored
Normal file
472
vendor/github.com/xenolf/lego/providers/dns/gandi/gandi.go
generated
vendored
Normal file
|
@ -0,0 +1,472 @@
|
|||
// Package gandi implements a DNS provider for solving the DNS-01
|
||||
// challenge using Gandi DNS.
|
||||
package gandi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/xenolf/lego/acme"
|
||||
)
|
||||
|
||||
// Gandi API reference: http://doc.rpc.gandi.net/index.html
|
||||
// Gandi API domain examples: http://doc.rpc.gandi.net/domain/faq.html
|
||||
|
||||
var (
|
||||
// endpoint is the Gandi XML-RPC endpoint used by Present and
|
||||
// CleanUp. It is overridden during tests.
|
||||
endpoint = "https://rpc.gandi.net/xmlrpc/"
|
||||
// findZoneByFqdn determines the DNS zone of an fqdn. It is overridden
|
||||
// during tests.
|
||||
findZoneByFqdn = acme.FindZoneByFqdn
|
||||
)
|
||||
|
||||
// inProgressInfo contains information about an in-progress challenge
|
||||
type inProgressInfo struct {
|
||||
zoneID int // zoneID of gandi zone to restore in CleanUp
|
||||
newZoneID int // zoneID of temporary gandi zone containing TXT record
|
||||
authZone string // the domain name registered at gandi with trailing "."
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the
|
||||
// acme.ChallengeProviderTimeout interface that uses Gandi's XML-RPC
|
||||
// API to manage TXT records for a domain.
|
||||
type DNSProvider struct {
|
||||
apiKey string
|
||||
inProgressFQDNs map[string]inProgressInfo
|
||||
inProgressAuthZones map[string]struct{}
|
||||
inProgressMu sync.Mutex
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for Gandi.
|
||||
// Credentials must be passed in the environment variable: GANDI_API_KEY.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
apiKey := os.Getenv("GANDI_API_KEY")
|
||||
return NewDNSProviderCredentials(apiKey)
|
||||
}
|
||||
|
||||
// NewDNSProviderCredentials uses the supplied credentials to return a
|
||||
// DNSProvider instance configured for Gandi.
|
||||
func NewDNSProviderCredentials(apiKey string) (*DNSProvider, error) {
|
||||
if apiKey == "" {
|
||||
return nil, fmt.Errorf("No Gandi API Key given")
|
||||
}
|
||||
return &DNSProvider{
|
||||
apiKey: apiKey,
|
||||
inProgressFQDNs: make(map[string]inProgressInfo),
|
||||
inProgressAuthZones: make(map[string]struct{}),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Present creates a TXT record using the specified parameters. It
|
||||
// does this by creating and activating a new temporary Gandi DNS
|
||||
// zone. This new zone contains the TXT record.
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value, ttl := acme.DNS01Record(domain, keyAuth)
|
||||
if ttl < 300 {
|
||||
ttl = 300 // 300 is gandi minimum value for ttl
|
||||
}
|
||||
// find authZone and Gandi zone_id for fqdn
|
||||
authZone, err := findZoneByFqdn(fqdn, acme.RecursiveNameservers)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Gandi DNS: findZoneByFqdn failure: %v", err)
|
||||
}
|
||||
zoneID, err := d.getZoneID(authZone)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// determine name of TXT record
|
||||
if !strings.HasSuffix(
|
||||
strings.ToLower(fqdn), strings.ToLower("."+authZone)) {
|
||||
return fmt.Errorf(
|
||||
"Gandi DNS: unexpected authZone %s for fqdn %s", authZone, fqdn)
|
||||
}
|
||||
name := fqdn[:len(fqdn)-len("."+authZone)]
|
||||
// acquire lock and check there is not a challenge already in
|
||||
// progress for this value of authZone
|
||||
d.inProgressMu.Lock()
|
||||
defer d.inProgressMu.Unlock()
|
||||
if _, ok := d.inProgressAuthZones[authZone]; ok {
|
||||
return fmt.Errorf(
|
||||
"Gandi DNS: challenge already in progress for authZone %s",
|
||||
authZone)
|
||||
}
|
||||
// perform API actions to create and activate new gandi zone
|
||||
// containing the required TXT record
|
||||
newZoneName := fmt.Sprintf(
|
||||
"%s [ACME Challenge %s]",
|
||||
acme.UnFqdn(authZone), time.Now().Format(time.RFC822Z))
|
||||
newZoneID, err := d.cloneZone(zoneID, newZoneName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newZoneVersion, err := d.newZoneVersion(newZoneID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = d.addTXTRecord(newZoneID, newZoneVersion, name, value, ttl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = d.setZoneVersion(newZoneID, newZoneVersion)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = d.setZone(authZone, newZoneID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// save data necessary for CleanUp
|
||||
d.inProgressFQDNs[fqdn] = inProgressInfo{
|
||||
zoneID: zoneID,
|
||||
newZoneID: newZoneID,
|
||||
authZone: authZone,
|
||||
}
|
||||
d.inProgressAuthZones[authZone] = struct{}{}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified
|
||||
// parameters. It does this by restoring the old Gandi DNS zone and
|
||||
// removing the temporary one created by Present.
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
|
||||
// acquire lock and retrieve zoneID, newZoneID and authZone
|
||||
d.inProgressMu.Lock()
|
||||
defer d.inProgressMu.Unlock()
|
||||
if _, ok := d.inProgressFQDNs[fqdn]; !ok {
|
||||
// if there is no cleanup information then just return
|
||||
return nil
|
||||
}
|
||||
zoneID := d.inProgressFQDNs[fqdn].zoneID
|
||||
newZoneID := d.inProgressFQDNs[fqdn].newZoneID
|
||||
authZone := d.inProgressFQDNs[fqdn].authZone
|
||||
delete(d.inProgressFQDNs, fqdn)
|
||||
delete(d.inProgressAuthZones, authZone)
|
||||
// perform API actions to restore old gandi zone for authZone
|
||||
err := d.setZone(authZone, zoneID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = d.deleteZone(newZoneID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Timeout returns the values (40*time.Minute, 60*time.Second) which
|
||||
// are used by the acme package as timeout and check interval values
|
||||
// when checking for DNS record propagation with Gandi.
|
||||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return 40 * time.Minute, 60 * time.Second
|
||||
}
|
||||
|
||||
// types for XML-RPC method calls and parameters
|
||||
|
||||
type param interface {
|
||||
param()
|
||||
}
|
||||
type paramString struct {
|
||||
XMLName xml.Name `xml:"param"`
|
||||
Value string `xml:"value>string"`
|
||||
}
|
||||
type paramInt struct {
|
||||
XMLName xml.Name `xml:"param"`
|
||||
Value int `xml:"value>int"`
|
||||
}
|
||||
|
||||
type structMember interface {
|
||||
structMember()
|
||||
}
|
||||
type structMemberString struct {
|
||||
Name string `xml:"name"`
|
||||
Value string `xml:"value>string"`
|
||||
}
|
||||
type structMemberInt struct {
|
||||
Name string `xml:"name"`
|
||||
Value int `xml:"value>int"`
|
||||
}
|
||||
type paramStruct struct {
|
||||
XMLName xml.Name `xml:"param"`
|
||||
StructMembers []structMember `xml:"value>struct>member"`
|
||||
}
|
||||
|
||||
func (p paramString) param() {}
|
||||
func (p paramInt) param() {}
|
||||
func (m structMemberString) structMember() {}
|
||||
func (m structMemberInt) structMember() {}
|
||||
func (p paramStruct) param() {}
|
||||
|
||||
type methodCall struct {
|
||||
XMLName xml.Name `xml:"methodCall"`
|
||||
MethodName string `xml:"methodName"`
|
||||
Params []param `xml:"params"`
|
||||
}
|
||||
|
||||
// types for XML-RPC responses
|
||||
|
||||
type response interface {
|
||||
faultCode() int
|
||||
faultString() string
|
||||
}
|
||||
|
||||
type responseFault struct {
|
||||
FaultCode int `xml:"fault>value>struct>member>value>int"`
|
||||
FaultString string `xml:"fault>value>struct>member>value>string"`
|
||||
}
|
||||
|
||||
func (r responseFault) faultCode() int { return r.FaultCode }
|
||||
func (r responseFault) faultString() string { return r.FaultString }
|
||||
|
||||
type responseStruct struct {
|
||||
responseFault
|
||||
StructMembers []struct {
|
||||
Name string `xml:"name"`
|
||||
ValueInt int `xml:"value>int"`
|
||||
} `xml:"params>param>value>struct>member"`
|
||||
}
|
||||
|
||||
type responseInt struct {
|
||||
responseFault
|
||||
Value int `xml:"params>param>value>int"`
|
||||
}
|
||||
|
||||
type responseBool struct {
|
||||
responseFault
|
||||
Value bool `xml:"params>param>value>boolean"`
|
||||
}
|
||||
|
||||
// POSTing/Marshalling/Unmarshalling
|
||||
|
||||
type rpcError struct {
|
||||
faultCode int
|
||||
faultString string
|
||||
}
|
||||
|
||||
func (e rpcError) Error() string {
|
||||
return fmt.Sprintf(
|
||||
"Gandi DNS: RPC Error: (%d) %s", e.faultCode, e.faultString)
|
||||
}
|
||||
|
||||
func httpPost(url string, bodyType string, body io.Reader) ([]byte, error) {
|
||||
client := http.Client{Timeout: 60 * time.Second}
|
||||
resp, err := client.Post(url, bodyType, body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Gandi DNS: HTTP Post Error: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Gandi DNS: HTTP Post Error: %v", err)
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// rpcCall makes an XML-RPC call to Gandi's RPC endpoint by
|
||||
// marshalling 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 rpcCall(call *methodCall, resp response) error {
|
||||
// marshal
|
||||
b, err := xml.MarshalIndent(call, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("Gandi DNS: Marshal Error: %v", err)
|
||||
}
|
||||
// post
|
||||
b = append([]byte(`<?xml version="1.0"?>`+"\n"), b...)
|
||||
respBody, err := httpPost(endpoint, "text/xml", bytes.NewReader(b))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// unmarshal
|
||||
err = xml.Unmarshal(respBody, resp)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Gandi DNS: 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 := rpcCall(&methodCall{
|
||||
MethodName: "domain.info",
|
||||
Params: []param{
|
||||
paramString{Value: d.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(
|
||||
"Gandi DNS: Could not determine zone_id for %s", domain)
|
||||
}
|
||||
return zoneID, nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) cloneZone(zoneID int, name string) (int, error) {
|
||||
resp := &responseStruct{}
|
||||
err := rpcCall(&methodCall{
|
||||
MethodName: "domain.zone.clone",
|
||||
Params: []param{
|
||||
paramString{Value: d.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("Gandi DNS: Could not determine cloned zone_id")
|
||||
}
|
||||
return newZoneID, nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) newZoneVersion(zoneID int) (int, error) {
|
||||
resp := &responseInt{}
|
||||
err := rpcCall(&methodCall{
|
||||
MethodName: "domain.zone.version.new",
|
||||
Params: []param{
|
||||
paramString{Value: d.apiKey},
|
||||
paramInt{Value: zoneID},
|
||||
},
|
||||
}, resp)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if resp.Value == 0 {
|
||||
return 0, fmt.Errorf("Gandi DNS: 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 := rpcCall(&methodCall{
|
||||
MethodName: "domain.zone.record.add",
|
||||
Params: []param{
|
||||
paramString{Value: d.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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) setZoneVersion(zoneID int, version int) error {
|
||||
resp := &responseBool{}
|
||||
err := rpcCall(&methodCall{
|
||||
MethodName: "domain.zone.version.set",
|
||||
Params: []param{
|
||||
paramString{Value: d.apiKey},
|
||||
paramInt{Value: zoneID},
|
||||
paramInt{Value: version},
|
||||
},
|
||||
}, resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !resp.Value {
|
||||
return fmt.Errorf("Gandi DNS: could not set zone version")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) setZone(domain string, zoneID int) error {
|
||||
resp := &responseStruct{}
|
||||
err := rpcCall(&methodCall{
|
||||
MethodName: "domain.zone.set",
|
||||
Params: []param{
|
||||
paramString{Value: d.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(
|
||||
"Gandi DNS: Could not set new zone_id for %s", domain)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) deleteZone(zoneID int) error {
|
||||
resp := &responseBool{}
|
||||
err := rpcCall(&methodCall{
|
||||
MethodName: "domain.zone.delete",
|
||||
Params: []param{
|
||||
paramString{Value: d.apiKey},
|
||||
paramInt{Value: zoneID},
|
||||
},
|
||||
}, resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !resp.Value {
|
||||
return fmt.Errorf("Gandi DNS: could not delete zone_id")
|
||||
}
|
||||
return nil
|
||||
}
|
168
vendor/github.com/xenolf/lego/providers/dns/googlecloud/googlecloud.go
generated
vendored
Normal file
168
vendor/github.com/xenolf/lego/providers/dns/googlecloud/googlecloud.go
generated
vendored
Normal file
|
@ -0,0 +1,168 @@
|
|||
// Package googlecloud implements a DNS provider for solving the DNS-01
|
||||
// challenge using Google Cloud DNS.
|
||||
package googlecloud
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/xenolf/lego/acme"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/oauth2/google"
|
||||
|
||||
"google.golang.org/api/dns/v1"
|
||||
)
|
||||
|
||||
// DNSProvider is an implementation of the DNSProvider interface.
|
||||
type DNSProvider struct {
|
||||
project string
|
||||
client *dns.Service
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for Google Cloud
|
||||
// DNS. Credentials must be passed in the environment variable: GCE_PROJECT.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
project := os.Getenv("GCE_PROJECT")
|
||||
return NewDNSProviderCredentials(project)
|
||||
}
|
||||
|
||||
// NewDNSProviderCredentials uses the supplied credentials to return a
|
||||
// DNSProvider instance configured for Google Cloud DNS.
|
||||
func NewDNSProviderCredentials(project string) (*DNSProvider, error) {
|
||||
if project == "" {
|
||||
return nil, fmt.Errorf("Google Cloud project name missing")
|
||||
}
|
||||
|
||||
client, err := google.DefaultClient(context.Background(), dns.NdevClouddnsReadwriteScope)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Unable to get Google Cloud client: %v", err)
|
||||
}
|
||||
svc, err := dns.New(client)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Unable to create Google Cloud DNS service: %v", err)
|
||||
}
|
||||
return &DNSProvider{
|
||||
project: project,
|
||||
client: svc,
|
||||
}, 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)
|
||||
|
||||
zone, err := c.getHostedZone(domain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rec := &dns.ResourceRecordSet{
|
||||
Name: fqdn,
|
||||
Rrdatas: []string{value},
|
||||
Ttl: int64(ttl),
|
||||
Type: "TXT",
|
||||
}
|
||||
change := &dns.Change{
|
||||
Additions: []*dns.ResourceRecordSet{rec},
|
||||
}
|
||||
|
||||
// Look for existing records.
|
||||
list, err := c.client.ResourceRecordSets.List(c.project, zone).Name(fqdn).Type("TXT").Do()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(list.Rrsets) > 0 {
|
||||
// Attempt to delete the existing records when adding our new one.
|
||||
change.Deletions = list.Rrsets
|
||||
}
|
||||
|
||||
chg, err := c.client.Changes.Create(c.project, zone, change).Do()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// wait for change to be acknowledged
|
||||
for chg.Status == "pending" {
|
||||
time.Sleep(time.Second)
|
||||
|
||||
chg, err = c.client.Changes.Get(c.project, zone, chg.Id).Do()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters.
|
||||
func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
|
||||
|
||||
zone, err := c.getHostedZone(domain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
records, err := c.findTxtRecords(zone, fqdn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, rec := range records {
|
||||
change := &dns.Change{
|
||||
Deletions: []*dns.ResourceRecordSet{rec},
|
||||
}
|
||||
_, err = c.client.Changes.Create(c.project, zone, change).Do()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Timeout customizes the timeout values used by the ACME package for checking
|
||||
// DNS record validity.
|
||||
func (c *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return 180 * time.Second, 5 * time.Second
|
||||
}
|
||||
|
||||
// getHostedZone returns the managed-zone
|
||||
func (c *DNSProvider) getHostedZone(domain string) (string, error) {
|
||||
authZone, err := acme.FindZoneByFqdn(acme.ToFqdn(domain), acme.RecursiveNameservers)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
zones, err := c.client.ManagedZones.
|
||||
List(c.project).
|
||||
DnsName(authZone).
|
||||
Do()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("GoogleCloud API call failed: %v", err)
|
||||
}
|
||||
|
||||
if len(zones.ManagedZones) == 0 {
|
||||
return "", fmt.Errorf("No matching GoogleCloud domain found for domain %s", authZone)
|
||||
}
|
||||
|
||||
return zones.ManagedZones[0].Name, nil
|
||||
}
|
||||
|
||||
func (c *DNSProvider) findTxtRecords(zone, fqdn string) ([]*dns.ResourceRecordSet, error) {
|
||||
|
||||
recs, err := c.client.ResourceRecordSets.List(c.project, zone).Do()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
found := []*dns.ResourceRecordSet{}
|
||||
for _, r := range recs.Rrsets {
|
||||
if r.Type == "TXT" && r.Name == fqdn {
|
||||
found = append(found, r)
|
||||
}
|
||||
}
|
||||
|
||||
return found, nil
|
||||
}
|
131
vendor/github.com/xenolf/lego/providers/dns/linode/linode.go
generated
vendored
Normal file
131
vendor/github.com/xenolf/lego/providers/dns/linode/linode.go
generated
vendored
Normal file
|
@ -0,0 +1,131 @@
|
|||
// Package linode implements a DNS provider for solving the DNS-01 challenge
|
||||
// using Linode DNS.
|
||||
package linode
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/timewasted/linode/dns"
|
||||
"github.com/xenolf/lego/acme"
|
||||
)
|
||||
|
||||
const (
|
||||
dnsMinTTLSecs = 300
|
||||
dnsUpdateFreqMins = 15
|
||||
dnsUpdateFudgeSecs = 120
|
||||
)
|
||||
|
||||
type hostedZoneInfo struct {
|
||||
domainId int
|
||||
resourceName string
|
||||
}
|
||||
|
||||
// DNSProvider implements the acme.ChallengeProvider interface.
|
||||
type DNSProvider struct {
|
||||
linode *dns.DNS
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for Linode.
|
||||
// Credentials must be passed in the environment variable: LINODE_API_KEY.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
apiKey := os.Getenv("LINODE_API_KEY")
|
||||
return NewDNSProviderCredentials(apiKey)
|
||||
}
|
||||
|
||||
// NewDNSProviderCredentials uses the supplied credentials to return a
|
||||
// DNSProvider instance configured for Linode.
|
||||
func NewDNSProviderCredentials(apiKey string) (*DNSProvider, error) {
|
||||
if len(apiKey) == 0 {
|
||||
return nil, errors.New("Linode credentials missing")
|
||||
}
|
||||
|
||||
return &DNSProvider{
|
||||
linode: dns.New(apiKey),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Timeout returns the timeout and interval to use when checking for DNS
|
||||
// propagation. Adjusting here to cope with spikes in propagation times.
|
||||
func (p *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
// Since Linode only updates their zone files every X minutes, we need
|
||||
// to figure out how many minutes we have to wait until we hit the next
|
||||
// interval of X. We then wait another couple of minutes, just to be
|
||||
// safe. Hopefully at some point during all of this, the record will
|
||||
// have propagated throughout Linode's network.
|
||||
minsRemaining := dnsUpdateFreqMins - (time.Now().Minute() % dnsUpdateFreqMins)
|
||||
|
||||
timeout = (time.Duration(minsRemaining) * time.Minute) +
|
||||
(dnsMinTTLSecs * time.Second) +
|
||||
(dnsUpdateFudgeSecs * time.Second)
|
||||
interval = 15 * time.Second
|
||||
return
|
||||
}
|
||||
|
||||
// Present creates a TXT record using the specified parameters.
|
||||
func (p *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
|
||||
zone, err := p.getHostedZoneInfo(fqdn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = p.linode.CreateDomainResourceTXT(zone.domainId, acme.UnFqdn(fqdn), value, 60); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters.
|
||||
func (p *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
|
||||
zone, err := p.getHostedZoneInfo(fqdn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get all TXT records for the specified domain.
|
||||
resources, err := p.linode.GetResourcesByType(zone.domainId, "TXT")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove the specified resource, if it exists.
|
||||
for _, resource := range resources {
|
||||
if resource.Name == zone.resourceName && resource.Target == value {
|
||||
resp, err := p.linode.DeleteDomainResource(resource.DomainID, resource.ResourceID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.ResourceID != resource.ResourceID {
|
||||
return errors.New("Error deleting resource: resource IDs do not match!")
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *DNSProvider) getHostedZoneInfo(fqdn string) (*hostedZoneInfo, error) {
|
||||
// Lookup the zone that handles the specified FQDN.
|
||||
authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resourceName := strings.TrimSuffix(fqdn, "."+authZone)
|
||||
|
||||
// Query the authority zone.
|
||||
domain, err := p.linode.GetDomain(acme.UnFqdn(authZone))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &hostedZoneInfo{
|
||||
domainId: domain.DomainID,
|
||||
resourceName: resourceName,
|
||||
}, nil
|
||||
}
|
416
vendor/github.com/xenolf/lego/providers/dns/namecheap/namecheap.go
generated
vendored
Normal file
416
vendor/github.com/xenolf/lego/providers/dns/namecheap/namecheap.go
generated
vendored
Normal file
|
@ -0,0 +1,416 @@
|
|||
// Package namecheap implements a DNS provider for solving the DNS-01
|
||||
// challenge using namecheap DNS.
|
||||
package namecheap
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/xenolf/lego/acme"
|
||||
)
|
||||
|
||||
// Notes about namecheap's tool API:
|
||||
// 1. Using the API requires registration. Once registered, use your account
|
||||
// name and API key to access the API.
|
||||
// 2. There is no API to add or modify a single DNS record. Instead you must
|
||||
// read the entire list of records, make modifications, and then write the
|
||||
// entire updated list of records. (Yuck.)
|
||||
// 3. Namecheap's DNS updates can be slow to propagate. I've seen them take
|
||||
// as long as an hour.
|
||||
// 4. Namecheap requires you to whitelist the IP address from which you call
|
||||
// its APIs. It also requires all API calls to include the whitelisted IP
|
||||
// address as a form or query string value. This code uses a namecheap
|
||||
// service to query the client's IP address.
|
||||
|
||||
var (
|
||||
debug = false
|
||||
defaultBaseURL = "https://api.namecheap.com/xml.response"
|
||||
getIPURL = "https://dynamicdns.park-your-domain.com/getip"
|
||||
httpClient = http.Client{Timeout: 60 * time.Second}
|
||||
)
|
||||
|
||||
// DNSProvider is an implementation of the ChallengeProviderTimeout interface
|
||||
// that uses Namecheap's tool API to manage TXT records for a domain.
|
||||
type DNSProvider struct {
|
||||
baseURL string
|
||||
apiUser string
|
||||
apiKey string
|
||||
clientIP string
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for namecheap.
|
||||
// Credentials must be passed in the environment variables: NAMECHEAP_API_USER
|
||||
// and NAMECHEAP_API_KEY.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
apiUser := os.Getenv("NAMECHEAP_API_USER")
|
||||
apiKey := os.Getenv("NAMECHEAP_API_KEY")
|
||||
return NewDNSProviderCredentials(apiUser, apiKey)
|
||||
}
|
||||
|
||||
// NewDNSProviderCredentials uses the supplied credentials to return a
|
||||
// DNSProvider instance configured for namecheap.
|
||||
func NewDNSProviderCredentials(apiUser, apiKey string) (*DNSProvider, error) {
|
||||
if apiUser == "" || apiKey == "" {
|
||||
return nil, fmt.Errorf("Namecheap credentials missing")
|
||||
}
|
||||
|
||||
clientIP, err := getClientIP()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &DNSProvider{
|
||||
baseURL: defaultBaseURL,
|
||||
apiUser: apiUser,
|
||||
apiKey: apiKey,
|
||||
clientIP: clientIP,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Timeout returns the timeout and interval to use when checking for DNS
|
||||
// propagation. Namecheap can sometimes take a long time to complete an
|
||||
// update, so wait up to 60 minutes for the update to propagate.
|
||||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return 60 * time.Minute, 15 * time.Second
|
||||
}
|
||||
|
||||
// host describes a DNS record returned by the Namecheap DNS gethosts API.
|
||||
// Namecheap uses the term "host" to refer to all DNS records that include
|
||||
// a host field (A, AAAA, CNAME, NS, TXT, URL).
|
||||
type host struct {
|
||||
Type string `xml:",attr"`
|
||||
Name string `xml:",attr"`
|
||||
Address string `xml:",attr"`
|
||||
MXPref string `xml:",attr"`
|
||||
TTL string `xml:",attr"`
|
||||
}
|
||||
|
||||
// apierror describes an error record in a namecheap API response.
|
||||
type apierror struct {
|
||||
Number int `xml:",attr"`
|
||||
Description string `xml:",innerxml"`
|
||||
}
|
||||
|
||||
// getClientIP returns the client's public IP address. It uses namecheap's
|
||||
// IP discovery service to perform the lookup.
|
||||
func getClientIP() (addr string, err error) {
|
||||
resp, err := httpClient.Get(getIPURL)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
clientIP, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if debug {
|
||||
fmt.Println("Client IP:", string(clientIP))
|
||||
}
|
||||
return string(clientIP), nil
|
||||
}
|
||||
|
||||
// A challenge repesents all the data needed to specify a dns-01 challenge
|
||||
// to lets-encrypt.
|
||||
type challenge struct {
|
||||
domain string
|
||||
key string
|
||||
keyFqdn string
|
||||
keyValue string
|
||||
tld string
|
||||
sld string
|
||||
host string
|
||||
}
|
||||
|
||||
// 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)
|
||||
parts := strings.Split(domain, ".")
|
||||
|
||||
// Find the longest matching TLD.
|
||||
longest := -1
|
||||
for i := len(parts); i > 0; i-- {
|
||||
t := strings.Join(parts[i-1:], ".")
|
||||
if _, found := tlds[t]; found {
|
||||
longest = i - 1
|
||||
}
|
||||
}
|
||||
if longest < 1 {
|
||||
return nil, fmt.Errorf("Invalid domain name '%s'", domain)
|
||||
}
|
||||
|
||||
tld := strings.Join(parts[longest:], ".")
|
||||
sld := parts[longest-1]
|
||||
|
||||
var host string
|
||||
if longest >= 1 {
|
||||
host = strings.Join(parts[:longest-1], ".")
|
||||
}
|
||||
|
||||
key, keyValue, _ := acme.DNS01Record(domain, keyAuth)
|
||||
|
||||
return &challenge{
|
||||
domain: domain,
|
||||
key: "_acme-challenge." + host,
|
||||
keyFqdn: key,
|
||||
keyValue: keyValue,
|
||||
tld: tld,
|
||||
sld: sld,
|
||||
host: host,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// setGlobalParams adds the namecheap global parameters to the provided url
|
||||
// Values record.
|
||||
func (d *DNSProvider) setGlobalParams(v *url.Values, cmd string) {
|
||||
v.Set("ApiUser", d.apiUser)
|
||||
v.Set("ApiKey", d.apiKey)
|
||||
v.Set("UserName", d.apiUser)
|
||||
v.Set("ClientIp", d.clientIP)
|
||||
v.Set("Command", cmd)
|
||||
}
|
||||
|
||||
// getTLDs requests the list of available TLDs from namecheap.
|
||||
func (d *DNSProvider) getTLDs() (tlds map[string]string, err error) {
|
||||
values := make(url.Values)
|
||||
d.setGlobalParams(&values, "namecheap.domains.getTldList")
|
||||
|
||||
reqURL, _ := url.Parse(d.baseURL)
|
||||
reqURL.RawQuery = values.Encode()
|
||||
|
||||
resp, err := httpClient.Get(reqURL.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode >= 400 {
|
||||
return nil, fmt.Errorf("getHosts HTTP error %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
type GetTldsResponse struct {
|
||||
XMLName xml.Name `xml:"ApiResponse"`
|
||||
Errors []apierror `xml:"Errors>Error"`
|
||||
Result []struct {
|
||||
Name string `xml:",attr"`
|
||||
} `xml:"CommandResponse>Tlds>Tld"`
|
||||
}
|
||||
|
||||
var gtr GetTldsResponse
|
||||
if err := xml.Unmarshal(body, >r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(gtr.Errors) > 0 {
|
||||
return nil, fmt.Errorf("Namecheap error: %s [%d]",
|
||||
gtr.Errors[0].Description, gtr.Errors[0].Number)
|
||||
}
|
||||
|
||||
tlds = make(map[string]string)
|
||||
for _, t := range gtr.Result {
|
||||
tlds[t.Name] = t.Name
|
||||
}
|
||||
return tlds, nil
|
||||
}
|
||||
|
||||
// getHosts reads the full list of DNS host records using the Namecheap API.
|
||||
func (d *DNSProvider) getHosts(ch *challenge) (hosts []host, err error) {
|
||||
values := make(url.Values)
|
||||
d.setGlobalParams(&values, "namecheap.domains.dns.getHosts")
|
||||
values.Set("SLD", ch.sld)
|
||||
values.Set("TLD", ch.tld)
|
||||
|
||||
reqURL, _ := url.Parse(d.baseURL)
|
||||
reqURL.RawQuery = values.Encode()
|
||||
|
||||
resp, err := httpClient.Get(reqURL.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode >= 400 {
|
||||
return nil, fmt.Errorf("getHosts HTTP error %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
type GetHostsResponse struct {
|
||||
XMLName xml.Name `xml:"ApiResponse"`
|
||||
Status string `xml:"Status,attr"`
|
||||
Errors []apierror `xml:"Errors>Error"`
|
||||
Hosts []host `xml:"CommandResponse>DomainDNSGetHostsResult>host"`
|
||||
}
|
||||
|
||||
var ghr GetHostsResponse
|
||||
if err = xml.Unmarshal(body, &ghr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(ghr.Errors) > 0 {
|
||||
return nil, fmt.Errorf("Namecheap error: %s [%d]",
|
||||
ghr.Errors[0].Description, ghr.Errors[0].Number)
|
||||
}
|
||||
|
||||
return ghr.Hosts, nil
|
||||
}
|
||||
|
||||
// setHosts writes the full list of DNS host records using the Namecheap API.
|
||||
func (d *DNSProvider) setHosts(ch *challenge, hosts []host) error {
|
||||
values := make(url.Values)
|
||||
d.setGlobalParams(&values, "namecheap.domains.dns.setHosts")
|
||||
values.Set("SLD", ch.sld)
|
||||
values.Set("TLD", ch.tld)
|
||||
|
||||
for i, h := range hosts {
|
||||
ind := fmt.Sprintf("%d", i+1)
|
||||
values.Add("HostName"+ind, h.Name)
|
||||
values.Add("RecordType"+ind, h.Type)
|
||||
values.Add("Address"+ind, h.Address)
|
||||
values.Add("MXPref"+ind, h.MXPref)
|
||||
values.Add("TTL"+ind, h.TTL)
|
||||
}
|
||||
|
||||
resp, err := httpClient.PostForm(d.baseURL, values)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode >= 400 {
|
||||
return fmt.Errorf("setHosts HTTP error %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
type SetHostsResponse struct {
|
||||
XMLName xml.Name `xml:"ApiResponse"`
|
||||
Status string `xml:"Status,attr"`
|
||||
Errors []apierror `xml:"Errors>Error"`
|
||||
Result struct {
|
||||
IsSuccess string `xml:",attr"`
|
||||
} `xml:"CommandResponse>DomainDNSSetHostsResult"`
|
||||
}
|
||||
|
||||
var shr SetHostsResponse
|
||||
if err := xml.Unmarshal(body, &shr); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(shr.Errors) > 0 {
|
||||
return fmt.Errorf("Namecheap error: %s [%d]",
|
||||
shr.Errors[0].Description, shr.Errors[0].Number)
|
||||
}
|
||||
if shr.Result.IsSuccess != "true" {
|
||||
return fmt.Errorf("Namecheap setHosts failed.")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// addChallengeRecord adds a DNS challenge TXT record to a list of namecheap
|
||||
// host records.
|
||||
func (d *DNSProvider) addChallengeRecord(ch *challenge, hosts *[]host) {
|
||||
host := host{
|
||||
Name: ch.key,
|
||||
Type: "TXT",
|
||||
Address: ch.keyValue,
|
||||
MXPref: "10",
|
||||
TTL: "120",
|
||||
}
|
||||
|
||||
// If there's already a TXT record with the same name, replace it.
|
||||
for i, h := range *hosts {
|
||||
if h.Name == ch.key && h.Type == "TXT" {
|
||||
(*hosts)[i] = host
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// No record was replaced, so add a new one.
|
||||
*hosts = append(*hosts, host)
|
||||
}
|
||||
|
||||
// removeChallengeRecord removes a DNS challenge TXT record from a list of
|
||||
// namecheap host records. Return true if a record was removed.
|
||||
func (d *DNSProvider) removeChallengeRecord(ch *challenge, hosts *[]host) bool {
|
||||
// Find the challenge TXT record and remove it if found.
|
||||
for i, h := range *hosts {
|
||||
if h.Name == ch.key && h.Type == "TXT" {
|
||||
*hosts = append((*hosts)[:i], (*hosts)[i+1:]...)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Present installs a TXT record for the DNS challenge.
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
tlds, err := d.getTLDs()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ch, err := newChallenge(domain, keyAuth, tlds)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hosts, err := d.getHosts(ch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.addChallengeRecord(ch, &hosts)
|
||||
|
||||
if debug {
|
||||
for _, h := range hosts {
|
||||
fmt.Printf(
|
||||
"%-5.5s %-30.30s %-6s %-70.70s\n",
|
||||
h.Type, h.Name, h.TTL, h.Address)
|
||||
}
|
||||
}
|
||||
|
||||
return d.setHosts(ch, hosts)
|
||||
}
|
||||
|
||||
// CleanUp removes a TXT record used for a previous DNS challenge.
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
tlds, err := d.getTLDs()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ch, err := newChallenge(domain, keyAuth, tlds)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hosts, err := d.getHosts(ch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if removed := d.removeChallengeRecord(ch, &hosts); !removed {
|
||||
return nil
|
||||
}
|
||||
|
||||
return d.setHosts(ch, hosts)
|
||||
}
|
97
vendor/github.com/xenolf/lego/providers/dns/ns1/ns1.go
generated
vendored
Normal file
97
vendor/github.com/xenolf/lego/providers/dns/ns1/ns1.go
generated
vendored
Normal file
|
@ -0,0 +1,97 @@
|
|||
// Package ns1 implements a DNS provider for solving the DNS-01 challenge
|
||||
// using NS1 DNS.
|
||||
package ns1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/xenolf/lego/acme"
|
||||
"gopkg.in/ns1/ns1-go.v2/rest"
|
||||
"gopkg.in/ns1/ns1-go.v2/rest/model/dns"
|
||||
)
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface.
|
||||
type DNSProvider struct {
|
||||
client *rest.Client
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for NS1.
|
||||
// Credentials must be passed in the environment variables: NS1_API_KEY.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
key := os.Getenv("NS1_API_KEY")
|
||||
if key == "" {
|
||||
return nil, fmt.Errorf("NS1 credentials missing")
|
||||
}
|
||||
return NewDNSProviderCredentials(key)
|
||||
}
|
||||
|
||||
// NewDNSProviderCredentials uses the supplied credentials to return a
|
||||
// DNSProvider instance configured for NS1.
|
||||
func NewDNSProviderCredentials(key string) (*DNSProvider, error) {
|
||||
if key == "" {
|
||||
return nil, fmt.Errorf("NS1 credentials missing")
|
||||
}
|
||||
|
||||
httpClient := &http.Client{Timeout: time.Second * 10}
|
||||
client := rest.NewClient(httpClient, rest.SetAPIKey(key))
|
||||
|
||||
return &DNSProvider{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)
|
||||
|
||||
zone, err := c.getHostedZone(domain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
record := c.newTxtRecord(zone, fqdn, value, ttl)
|
||||
_, err = c.client.Records.Create(record)
|
||||
if err != nil && err != rest.ErrRecordExists {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters.
|
||||
func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
|
||||
|
||||
zone, err := c.getHostedZone(domain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
name := acme.UnFqdn(fqdn)
|
||||
_, err = c.client.Records.Delete(zone.Zone, name, "TXT")
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *DNSProvider) getHostedZone(domain string) (*dns.Zone, error) {
|
||||
zone, _, err := c.client.Zones.Get(domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return zone, nil
|
||||
}
|
||||
|
||||
func (c *DNSProvider) newTxtRecord(zone *dns.Zone, fqdn, value string, ttl int) *dns.Record {
|
||||
name := acme.UnFqdn(fqdn)
|
||||
|
||||
return &dns.Record{
|
||||
Type: "TXT",
|
||||
Zone: zone.Zone,
|
||||
Domain: name,
|
||||
TTL: ttl,
|
||||
Answers: []*dns.Answer{
|
||||
{Rdata: []string{value}},
|
||||
},
|
||||
}
|
||||
}
|
159
vendor/github.com/xenolf/lego/providers/dns/ovh/ovh.go
generated
vendored
Normal file
159
vendor/github.com/xenolf/lego/providers/dns/ovh/ovh.go
generated
vendored
Normal file
|
@ -0,0 +1,159 @@
|
|||
// Package OVH implements a DNS provider for solving the DNS-01
|
||||
// challenge using OVH DNS.
|
||||
package ovh
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/ovh/go-ovh/ovh"
|
||||
"github.com/xenolf/lego/acme"
|
||||
)
|
||||
|
||||
// OVH API reference: https://eu.api.ovh.com/
|
||||
// Create a Token: https://eu.api.ovh.com/createToken/
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface
|
||||
// that uses OVH's REST API to manage TXT records for a domain.
|
||||
type DNSProvider struct {
|
||||
client *ovh.Client
|
||||
recordIDs map[string]int
|
||||
recordIDsMu sync.Mutex
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for OVH
|
||||
// Credentials must be passed in the environment variable:
|
||||
// OVH_ENDPOINT : it must be ovh-eu or ovh-ca
|
||||
// OVH_APPLICATION_KEY
|
||||
// OVH_APPLICATION_SECRET
|
||||
// OVH_CONSUMER_KEY
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
apiEndpoint := os.Getenv("OVH_ENDPOINT")
|
||||
applicationKey := os.Getenv("OVH_APPLICATION_KEY")
|
||||
applicationSecret := os.Getenv("OVH_APPLICATION_SECRET")
|
||||
consumerKey := os.Getenv("OVH_CONSUMER_KEY")
|
||||
return NewDNSProviderCredentials(apiEndpoint, applicationKey, applicationSecret, consumerKey)
|
||||
}
|
||||
|
||||
// NewDNSProviderCredentials uses the supplied credentials to return a
|
||||
// DNSProvider instance configured for OVH.
|
||||
func NewDNSProviderCredentials(apiEndpoint, applicationKey, applicationSecret, consumerKey string) (*DNSProvider, error) {
|
||||
if apiEndpoint == "" || applicationKey == "" || applicationSecret == "" || consumerKey == "" {
|
||||
return nil, fmt.Errorf("OVH credentials missing")
|
||||
}
|
||||
|
||||
ovhClient, _ := ovh.NewClient(
|
||||
apiEndpoint,
|
||||
applicationKey,
|
||||
applicationSecret,
|
||||
consumerKey,
|
||||
)
|
||||
|
||||
return &DNSProvider{
|
||||
client: ovhClient,
|
||||
recordIDs: make(map[string]int),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfil the dns-01 challenge.
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
|
||||
// txtRecordRequest represents the request body to DO's API to make a TXT record
|
||||
type txtRecordRequest struct {
|
||||
FieldType string `json:"fieldType"`
|
||||
SubDomain string `json:"subDomain"`
|
||||
Target string `json:"target"`
|
||||
TTL int `json:"ttl"`
|
||||
}
|
||||
|
||||
// txtRecordResponse represents a response from DO's API after making a TXT record
|
||||
type txtRecordResponse struct {
|
||||
ID int `json:"id"`
|
||||
FieldType string `json:"fieldType"`
|
||||
SubDomain string `json:"subDomain"`
|
||||
Target string `json:"target"`
|
||||
TTL int `json:"ttl"`
|
||||
Zone string `json:"zone"`
|
||||
}
|
||||
|
||||
fqdn, value, ttl := acme.DNS01Record(domain, keyAuth)
|
||||
|
||||
// Parse domain name
|
||||
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)
|
||||
}
|
||||
|
||||
authZone = acme.UnFqdn(authZone)
|
||||
subDomain := d.extractRecordName(fqdn, authZone)
|
||||
|
||||
reqURL := fmt.Sprintf("/domain/zone/%s/record", authZone)
|
||||
reqData := txtRecordRequest{FieldType: "TXT", SubDomain: subDomain, Target: value, TTL: ttl}
|
||||
var respData txtRecordResponse
|
||||
|
||||
// Create TXT record
|
||||
err = d.client.Post(reqURL, reqData, &respData)
|
||||
if err != nil {
|
||||
fmt.Printf("Error when call OVH api to add record : %q \n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Apply the change
|
||||
reqURL = fmt.Sprintf("/domain/zone/%s/refresh", authZone)
|
||||
err = d.client.Post(reqURL, nil, nil)
|
||||
if err != nil {
|
||||
fmt.Printf("Error when call OVH api to refresh zone : %q \n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
d.recordIDsMu.Lock()
|
||||
d.recordIDs[fqdn] = respData.ID
|
||||
d.recordIDsMu.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
|
||||
|
||||
// get the record's unique ID from when we created it
|
||||
d.recordIDsMu.Lock()
|
||||
recordID, ok := d.recordIDs[fqdn]
|
||||
d.recordIDsMu.Unlock()
|
||||
if !ok {
|
||||
return fmt.Errorf("unknown record ID for '%s'", fqdn)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
authZone = acme.UnFqdn(authZone)
|
||||
|
||||
reqURL := fmt.Sprintf("/domain/zone/%s/record/%d", authZone, recordID)
|
||||
|
||||
err = d.client.Delete(reqURL, nil)
|
||||
if err != nil {
|
||||
fmt.Printf("Error when call OVH api to delete challenge record : %q \n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete record ID from map
|
||||
d.recordIDsMu.Lock()
|
||||
delete(d.recordIDs, fqdn)
|
||||
d.recordIDsMu.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
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
|
||||
}
|
343
vendor/github.com/xenolf/lego/providers/dns/pdns/pdns.go
generated
vendored
Normal file
343
vendor/github.com/xenolf/lego/providers/dns/pdns/pdns.go
generated
vendored
Normal file
|
@ -0,0 +1,343 @@
|
|||
// Package pdns implements a DNS provider for solving the DNS-01
|
||||
// challenge using PowerDNS nameserver.
|
||||
package pdns
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/xenolf/lego/acme"
|
||||
)
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface
|
||||
type DNSProvider struct {
|
||||
apiKey string
|
||||
host *url.URL
|
||||
apiVersion int
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for pdns.
|
||||
// Credentials must be passed in the environment variable:
|
||||
// PDNS_API_URL and PDNS_API_KEY.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
key := os.Getenv("PDNS_API_KEY")
|
||||
hostUrl, err := url.Parse(os.Getenv("PDNS_API_URL"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewDNSProviderCredentials(hostUrl, key)
|
||||
}
|
||||
|
||||
// NewDNSProviderCredentials uses the supplied credentials to return a
|
||||
// DNSProvider instance configured for pdns.
|
||||
func NewDNSProviderCredentials(host *url.URL, key string) (*DNSProvider, error) {
|
||||
if key == "" {
|
||||
return nil, fmt.Errorf("PDNS API key missing")
|
||||
}
|
||||
|
||||
if host == nil || host.Host == "" {
|
||||
return nil, fmt.Errorf("PDNS API URL missing")
|
||||
}
|
||||
|
||||
provider := &DNSProvider{
|
||||
host: host,
|
||||
apiKey: key,
|
||||
}
|
||||
provider.getAPIVersion()
|
||||
|
||||
return provider, nil
|
||||
}
|
||||
|
||||
// Timeout returns the timeout and interval to use when checking for DNS
|
||||
// propagation. Adjusting here to cope with spikes in propagation times.
|
||||
func (c *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return 120 * time.Second, 2 * time.Second
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfil the dns-01 challenge
|
||||
func (c *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
|
||||
zone, err := c.getHostedZone(fqdn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
name := fqdn
|
||||
|
||||
// pre-v1 API wants non-fqdn
|
||||
if c.apiVersion == 0 {
|
||||
name = acme.UnFqdn(fqdn)
|
||||
}
|
||||
|
||||
rec := pdnsRecord{
|
||||
Content: "\"" + value + "\"",
|
||||
Disabled: false,
|
||||
|
||||
// pre-v1 API
|
||||
Type: "TXT",
|
||||
Name: name,
|
||||
TTL: 120,
|
||||
}
|
||||
|
||||
rrsets := rrSets{
|
||||
RRSets: []rrSet{
|
||||
rrSet{
|
||||
Name: name,
|
||||
ChangeType: "REPLACE",
|
||||
Type: "TXT",
|
||||
Kind: "Master",
|
||||
TTL: 120,
|
||||
Records: []pdnsRecord{rec},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
body, err := json.Marshal(rrsets)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = c.makeRequest("PATCH", zone.URL, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
fmt.Println("here")
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters
|
||||
func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
|
||||
|
||||
zone, err := c.getHostedZone(fqdn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
set, err := c.findTxtRecord(fqdn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rrsets := rrSets{
|
||||
RRSets: []rrSet{
|
||||
rrSet{
|
||||
Name: set.Name,
|
||||
Type: set.Type,
|
||||
ChangeType: "DELETE",
|
||||
},
|
||||
},
|
||||
}
|
||||
body, err := json.Marshal(rrsets)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = c.makeRequest("PATCH", zone.URL, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *DNSProvider) getHostedZone(fqdn string) (*hostedZone, error) {
|
||||
var zone hostedZone
|
||||
authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
url := "/servers/localhost/zones"
|
||||
result, err := c.makeRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
zones := []hostedZone{}
|
||||
err = json.Unmarshal(result, &zones)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
url = ""
|
||||
for _, zone := range zones {
|
||||
if acme.UnFqdn(zone.Name) == acme.UnFqdn(authZone) {
|
||||
url = zone.URL
|
||||
}
|
||||
}
|
||||
|
||||
result, err = c.makeRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(result, &zone)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// convert pre-v1 API result
|
||||
if len(zone.Records) > 0 {
|
||||
zone.RRSets = []rrSet{}
|
||||
for _, record := range zone.Records {
|
||||
set := rrSet{
|
||||
Name: record.Name,
|
||||
Type: record.Type,
|
||||
Records: []pdnsRecord{record},
|
||||
}
|
||||
zone.RRSets = append(zone.RRSets, set)
|
||||
}
|
||||
}
|
||||
|
||||
return &zone, nil
|
||||
}
|
||||
|
||||
func (c *DNSProvider) findTxtRecord(fqdn string) (*rrSet, error) {
|
||||
zone, err := c.getHostedZone(fqdn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = c.makeRequest("GET", zone.URL, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, set := range zone.RRSets {
|
||||
if (set.Name == acme.UnFqdn(fqdn) || set.Name == fqdn) && set.Type == "TXT" {
|
||||
return &set, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("No existing record found for %s", fqdn)
|
||||
}
|
||||
|
||||
func (c *DNSProvider) getAPIVersion() {
|
||||
type APIVersion struct {
|
||||
URL string `json:"url"`
|
||||
Version int `json:"version"`
|
||||
}
|
||||
|
||||
result, err := c.makeRequest("GET", "/api", nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var versions []APIVersion
|
||||
err = json.Unmarshal(result, &versions)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
latestVersion := 0
|
||||
for _, v := range versions {
|
||||
if v.Version > latestVersion {
|
||||
latestVersion = v.Version
|
||||
}
|
||||
}
|
||||
c.apiVersion = latestVersion
|
||||
}
|
||||
|
||||
func (c *DNSProvider) makeRequest(method, uri string, body io.Reader) (json.RawMessage, error) {
|
||||
type APIError struct {
|
||||
Error string `json:"error"`
|
||||
}
|
||||
var path = ""
|
||||
if c.host.Path != "/" {
|
||||
path = c.host.Path
|
||||
}
|
||||
if c.apiVersion > 0 {
|
||||
if !strings.HasPrefix(uri, "api/v") {
|
||||
uri = "/api/v" + strconv.Itoa(c.apiVersion) + uri
|
||||
} else {
|
||||
uri = "/" + uri
|
||||
}
|
||||
}
|
||||
url := c.host.Scheme + "://" + c.host.Host + path + uri
|
||||
req, err := http.NewRequest(method, url, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("X-API-Key", c.apiKey)
|
||||
|
||||
client := http.Client{Timeout: 30 * time.Second}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error talking to PDNS API -> %v", err)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 422 && (resp.StatusCode < 200 || resp.StatusCode >= 300) {
|
||||
return nil, fmt.Errorf("Unexpected HTTP status code %d when fetching '%s'", resp.StatusCode, url)
|
||||
}
|
||||
|
||||
var msg json.RawMessage
|
||||
err = json.NewDecoder(resp.Body).Decode(&msg)
|
||||
switch {
|
||||
case err == io.EOF:
|
||||
// empty body
|
||||
return nil, nil
|
||||
case err != nil:
|
||||
// other error
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// check for PowerDNS error message
|
||||
if len(msg) > 0 && msg[0] == '{' {
|
||||
var apiError APIError
|
||||
err = json.Unmarshal(msg, &apiError)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if apiError.Error != "" {
|
||||
return nil, fmt.Errorf("Error talking to PDNS API -> %v", apiError.Error)
|
||||
}
|
||||
}
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
type pdnsRecord struct {
|
||||
Content string `json:"content"`
|
||||
Disabled bool `json:"disabled"`
|
||||
|
||||
// pre-v1 API
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
TTL int `json:"ttl,omitempty"`
|
||||
}
|
||||
|
||||
type hostedZone struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
URL string `json:"url"`
|
||||
RRSets []rrSet `json:"rrsets"`
|
||||
|
||||
// pre-v1 API
|
||||
Records []pdnsRecord `json:"records"`
|
||||
}
|
||||
|
||||
type rrSet struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Kind string `json:"kind"`
|
||||
ChangeType string `json:"changetype"`
|
||||
Records []pdnsRecord `json:"records"`
|
||||
TTL int `json:"ttl,omitempty"`
|
||||
}
|
||||
|
||||
type rrSets struct {
|
||||
RRSets []rrSet `json:"rrsets"`
|
||||
}
|
284
vendor/github.com/xenolf/lego/providers/dns/rackspace/rackspace.go
generated
vendored
Normal file
284
vendor/github.com/xenolf/lego/providers/dns/rackspace/rackspace.go
generated
vendored
Normal file
|
@ -0,0 +1,284 @@
|
|||
// Package rackspace implements a DNS provider for solving the DNS-01
|
||||
// challenge using rackspace DNS.
|
||||
package rackspace
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/xenolf/lego/acme"
|
||||
)
|
||||
|
||||
// rackspaceAPIURL represents the Identity API endpoint to call
|
||||
var rackspaceAPIURL = "https://identity.api.rackspacecloud.com/v2.0/tokens"
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface
|
||||
// used to store the reusable token and DNS API endpoint
|
||||
type DNSProvider struct {
|
||||
token string
|
||||
cloudDNSEndpoint string
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for Rackspace.
|
||||
// Credentials must be passed in the environment variables: RACKSPACE_USER
|
||||
// and RACKSPACE_API_KEY.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
user := os.Getenv("RACKSPACE_USER")
|
||||
key := os.Getenv("RACKSPACE_API_KEY")
|
||||
return NewDNSProviderCredentials(user, key)
|
||||
}
|
||||
|
||||
// NewDNSProviderCredentials uses the supplied credentials to return a
|
||||
// DNSProvider instance configured for Rackspace. It authenticates against
|
||||
// the API, also grabbing the DNS Endpoint.
|
||||
func NewDNSProviderCredentials(user, key string) (*DNSProvider, error) {
|
||||
if user == "" || key == "" {
|
||||
return nil, fmt.Errorf("Rackspace credentials missing")
|
||||
}
|
||||
|
||||
type APIKeyCredentials struct {
|
||||
Username string `json:"username"`
|
||||
APIKey string `json:"apiKey"`
|
||||
}
|
||||
|
||||
type Auth struct {
|
||||
APIKeyCredentials `json:"RAX-KSKEY:apiKeyCredentials"`
|
||||
}
|
||||
|
||||
type RackspaceAuthData struct {
|
||||
Auth `json:"auth"`
|
||||
}
|
||||
|
||||
type RackspaceIdentity struct {
|
||||
Access struct {
|
||||
ServiceCatalog []struct {
|
||||
Endpoints []struct {
|
||||
PublicURL string `json:"publicURL"`
|
||||
TenantID string `json:"tenantId"`
|
||||
} `json:"endpoints"`
|
||||
Name string `json:"name"`
|
||||
} `json:"serviceCatalog"`
|
||||
Token struct {
|
||||
ID string `json:"id"`
|
||||
} `json:"token"`
|
||||
} `json:"access"`
|
||||
}
|
||||
|
||||
authData := RackspaceAuthData{
|
||||
Auth: Auth{
|
||||
APIKeyCredentials: APIKeyCredentials{
|
||||
Username: user,
|
||||
APIKey: key,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
body, err := json.Marshal(authData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", rackspaceAPIURL, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
client := http.Client{Timeout: 30 * time.Second}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error querying Rackspace Identity API: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("Rackspace Authentication failed. Response code: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var rackspaceIdentity RackspaceIdentity
|
||||
err = json.NewDecoder(resp.Body).Decode(&rackspaceIdentity)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Iterate through the Service Catalog to get the DNS Endpoint
|
||||
var dnsEndpoint string
|
||||
for _, service := range rackspaceIdentity.Access.ServiceCatalog {
|
||||
if service.Name == "cloudDNS" {
|
||||
dnsEndpoint = service.Endpoints[0].PublicURL
|
||||
break
|
||||
}
|
||||
}
|
||||
if dnsEndpoint == "" {
|
||||
return nil, fmt.Errorf("Failed to populate DNS endpoint, check Rackspace API for changes.")
|
||||
}
|
||||
|
||||
return &DNSProvider{
|
||||
token: rackspaceIdentity.Access.Token.ID,
|
||||
cloudDNSEndpoint: dnsEndpoint,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfil the dns-01 challenge
|
||||
func (c *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
|
||||
zoneID, err := c.getHostedZoneID(fqdn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rec := RackspaceRecords{
|
||||
RackspaceRecord: []RackspaceRecord{{
|
||||
Name: acme.UnFqdn(fqdn),
|
||||
Type: "TXT",
|
||||
Data: value,
|
||||
TTL: 300,
|
||||
}},
|
||||
}
|
||||
|
||||
body, err := json.Marshal(rec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = c.makeRequest("POST", fmt.Sprintf("/domains/%d/records", zoneID), bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters
|
||||
func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
|
||||
zoneID, err := c.getHostedZoneID(fqdn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
record, err := c.findTxtRecord(fqdn, zoneID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = c.makeRequest("DELETE", fmt.Sprintf("/domains/%d/records?id=%s", zoneID, record.ID), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getHostedZoneID performs a lookup to get the DNS zone which needs
|
||||
// modifying for a given FQDN
|
||||
func (c *DNSProvider) getHostedZoneID(fqdn string) (int, error) {
|
||||
// HostedZones represents the response when querying Rackspace DNS zones
|
||||
type ZoneSearchResponse struct {
|
||||
TotalEntries int `json:"totalEntries"`
|
||||
HostedZones []struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
} `json:"domains"`
|
||||
}
|
||||
|
||||
authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
result, err := c.makeRequest("GET", fmt.Sprintf("/domains?name=%s", acme.UnFqdn(authZone)), nil)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var zoneSearchResponse ZoneSearchResponse
|
||||
err = json.Unmarshal(result, &zoneSearchResponse)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// If nothing was returned, or for whatever reason more than 1 was returned (the search uses exact match, so should not occur)
|
||||
if zoneSearchResponse.TotalEntries != 1 {
|
||||
return 0, fmt.Errorf("Found %d zones for %s in Rackspace for domain %s", zoneSearchResponse.TotalEntries, authZone, fqdn)
|
||||
}
|
||||
|
||||
return zoneSearchResponse.HostedZones[0].ID, nil
|
||||
}
|
||||
|
||||
// findTxtRecord searches a DNS zone for a TXT record with a specific name
|
||||
func (c *DNSProvider) findTxtRecord(fqdn string, zoneID int) (*RackspaceRecord, error) {
|
||||
result, err := c.makeRequest("GET", fmt.Sprintf("/domains/%d/records?type=TXT&name=%s", zoneID, acme.UnFqdn(fqdn)), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var records RackspaceRecords
|
||||
err = json.Unmarshal(result, &records)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
recordsLength := len(records.RackspaceRecord)
|
||||
switch recordsLength {
|
||||
case 1:
|
||||
break
|
||||
case 0:
|
||||
return nil, fmt.Errorf("No TXT record found for %s", fqdn)
|
||||
default:
|
||||
return nil, fmt.Errorf("More than 1 TXT record found for %s", fqdn)
|
||||
}
|
||||
|
||||
return &records.RackspaceRecord[0], nil
|
||||
}
|
||||
|
||||
// makeRequest is a wrapper function used for making DNS API requests
|
||||
func (c *DNSProvider) makeRequest(method, uri string, body io.Reader) (json.RawMessage, error) {
|
||||
url := c.cloudDNSEndpoint + uri
|
||||
req, err := http.NewRequest(method, url, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("X-Auth-Token", c.token)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
client := http.Client{Timeout: 30 * time.Second}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error querying DNS API: %v", err)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusAccepted {
|
||||
return nil, fmt.Errorf("Request failed for %s %s. Response code: %d", method, url, resp.StatusCode)
|
||||
}
|
||||
|
||||
var r json.RawMessage
|
||||
err = json.NewDecoder(resp.Body).Decode(&r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("JSON decode failed for %s %s. Response code: %d", method, url, resp.StatusCode)
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// RackspaceRecords is the list of records sent/recieved from the DNS API
|
||||
type RackspaceRecords struct {
|
||||
RackspaceRecord []RackspaceRecord `json:"records"`
|
||||
}
|
||||
|
||||
// RackspaceRecord represents a Rackspace DNS record
|
||||
type RackspaceRecord struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Data string `json:"data"`
|
||||
TTL int `json:"ttl,omitempty"`
|
||||
ID string `json:"id,omitempty"`
|
||||
}
|
129
vendor/github.com/xenolf/lego/providers/dns/rfc2136/rfc2136.go
generated
vendored
Normal file
129
vendor/github.com/xenolf/lego/providers/dns/rfc2136/rfc2136.go
generated
vendored
Normal file
|
@ -0,0 +1,129 @@
|
|||
// Package rfc2136 implements a DNS provider for solving the DNS-01 challenge
|
||||
// using the rfc2136 dynamic update.
|
||||
package rfc2136
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/xenolf/lego/acme"
|
||||
)
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface that
|
||||
// uses dynamic DNS updates (RFC 2136) to create TXT records on a nameserver.
|
||||
type DNSProvider struct {
|
||||
nameserver string
|
||||
tsigAlgorithm string
|
||||
tsigKey string
|
||||
tsigSecret string
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for rfc2136
|
||||
// dynamic update. Credentials must be passed in the environment variables:
|
||||
// RFC2136_NAMESERVER, RFC2136_TSIG_ALGORITHM, RFC2136_TSIG_KEY and
|
||||
// RFC2136_TSIG_SECRET. To disable TSIG authentication, leave the TSIG
|
||||
// variables unset. RFC2136_NAMESERVER must be a network address in the form
|
||||
// "host" or "host:port".
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
nameserver := os.Getenv("RFC2136_NAMESERVER")
|
||||
tsigAlgorithm := os.Getenv("RFC2136_TSIG_ALGORITHM")
|
||||
tsigKey := os.Getenv("RFC2136_TSIG_KEY")
|
||||
tsigSecret := os.Getenv("RFC2136_TSIG_SECRET")
|
||||
return NewDNSProviderCredentials(nameserver, tsigAlgorithm, tsigKey, tsigSecret)
|
||||
}
|
||||
|
||||
// NewDNSProviderCredentials uses the supplied credentials to return a
|
||||
// DNSProvider instance configured for rfc2136 dynamic update. To disable TSIG
|
||||
// authentication, leave the TSIG parameters as empty strings.
|
||||
// nameserver must be a network address in the form "host" or "host:port".
|
||||
func NewDNSProviderCredentials(nameserver, tsigAlgorithm, tsigKey, tsigSecret string) (*DNSProvider, error) {
|
||||
if nameserver == "" {
|
||||
return nil, fmt.Errorf("RFC2136 nameserver missing")
|
||||
}
|
||||
|
||||
// Append the default DNS port if none is specified.
|
||||
if _, _, err := net.SplitHostPort(nameserver); err != nil {
|
||||
if strings.Contains(err.Error(), "missing port") {
|
||||
nameserver = net.JoinHostPort(nameserver, "53")
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
d := &DNSProvider{
|
||||
nameserver: nameserver,
|
||||
}
|
||||
if tsigAlgorithm == "" {
|
||||
tsigAlgorithm = dns.HmacMD5
|
||||
}
|
||||
d.tsigAlgorithm = tsigAlgorithm
|
||||
if len(tsigKey) > 0 && len(tsigSecret) > 0 {
|
||||
d.tsigKey = tsigKey
|
||||
d.tsigSecret = tsigSecret
|
||||
}
|
||||
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// Present creates a TXT record using the specified parameters
|
||||
func (r *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value, ttl := acme.DNS01Record(domain, keyAuth)
|
||||
return r.changeRecord("INSERT", fqdn, value, ttl)
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters
|
||||
func (r *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, value, ttl := acme.DNS01Record(domain, keyAuth)
|
||||
return r.changeRecord("REMOVE", fqdn, value, ttl)
|
||||
}
|
||||
|
||||
func (r *DNSProvider) changeRecord(action, fqdn, value string, ttl int) error {
|
||||
// Find the zone for the given fqdn
|
||||
zone, err := acme.FindZoneByFqdn(fqdn, []string{r.nameserver})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create RR
|
||||
rr := new(dns.TXT)
|
||||
rr.Hdr = dns.RR_Header{Name: fqdn, Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: uint32(ttl)}
|
||||
rr.Txt = []string{value}
|
||||
rrs := []dns.RR{rr}
|
||||
|
||||
// Create dynamic update packet
|
||||
m := new(dns.Msg)
|
||||
m.SetUpdate(zone)
|
||||
switch action {
|
||||
case "INSERT":
|
||||
// Always remove old challenge left over from who knows what.
|
||||
m.RemoveRRset(rrs)
|
||||
m.Insert(rrs)
|
||||
case "REMOVE":
|
||||
m.Remove(rrs)
|
||||
default:
|
||||
return fmt.Errorf("Unexpected action: %s", action)
|
||||
}
|
||||
|
||||
// Setup client
|
||||
c := new(dns.Client)
|
||||
c.SingleInflight = true
|
||||
// TSIG authentication / msg signing
|
||||
if len(r.tsigKey) > 0 && len(r.tsigSecret) > 0 {
|
||||
m.SetTsig(dns.Fqdn(r.tsigKey), r.tsigAlgorithm, 300, time.Now().Unix())
|
||||
c.TsigSecret = map[string]string{dns.Fqdn(r.tsigKey): r.tsigSecret}
|
||||
}
|
||||
|
||||
// Send the query
|
||||
reply, _, err := c.Exchange(m, r.nameserver)
|
||||
if err != nil {
|
||||
return fmt.Errorf("DNS update failed: %v", err)
|
||||
}
|
||||
if reply != nil && reply.Rcode != dns.RcodeSuccess {
|
||||
return fmt.Errorf("DNS update failed. Server replied: %s", dns.RcodeToString[reply.Rcode])
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
171
vendor/github.com/xenolf/lego/providers/dns/route53/route53.go
generated
vendored
Normal file
171
vendor/github.com/xenolf/lego/providers/dns/route53/route53.go
generated
vendored
Normal file
|
@ -0,0 +1,171 @@
|
|||
// Package route53 implements a DNS provider for solving the DNS-01 challenge
|
||||
// using AWS Route 53 DNS.
|
||||
package route53
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/client"
|
||||
"github.com/aws/aws-sdk-go/aws/request"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/route53"
|
||||
"github.com/xenolf/lego/acme"
|
||||
)
|
||||
|
||||
const (
|
||||
maxRetries = 5
|
||||
route53TTL = 10
|
||||
)
|
||||
|
||||
// DNSProvider implements the acme.ChallengeProvider interface
|
||||
type DNSProvider struct {
|
||||
client *route53.Route53
|
||||
}
|
||||
|
||||
// customRetryer implements the client.Retryer interface by composing the
|
||||
// DefaultRetryer. It controls the logic for retrying recoverable request
|
||||
// errors (e.g. when rate limits are exceeded).
|
||||
type customRetryer struct {
|
||||
client.DefaultRetryer
|
||||
}
|
||||
|
||||
// RetryRules overwrites the DefaultRetryer's method.
|
||||
// It uses a basic exponential backoff algorithm that returns an initial
|
||||
// delay of ~400ms with an upper limit of ~30 seconds which should prevent
|
||||
// causing a high number of consecutive throttling errors.
|
||||
// For reference: Route 53 enforces an account-wide(!) 5req/s query limit.
|
||||
func (d customRetryer) RetryRules(r *request.Request) time.Duration {
|
||||
retryCount := r.RetryCount
|
||||
if retryCount > 7 {
|
||||
retryCount = 7
|
||||
}
|
||||
|
||||
delay := (1 << uint(retryCount)) * (rand.Intn(50) + 200)
|
||||
return time.Duration(delay) * time.Millisecond
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for the AWS
|
||||
// Route 53 service.
|
||||
//
|
||||
// AWS Credentials are automatically detected in the following locations
|
||||
// and prioritized in the following order:
|
||||
// 1. Environment variables: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY,
|
||||
// AWS_REGION, [AWS_SESSION_TOKEN]
|
||||
// 2. Shared credentials file (defaults to ~/.aws/credentials)
|
||||
// 3. Amazon EC2 IAM role
|
||||
//
|
||||
// See also: https://github.com/aws/aws-sdk-go/wiki/configuring-sdk
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
r := customRetryer{}
|
||||
r.NumMaxRetries = maxRetries
|
||||
config := request.WithRetryer(aws.NewConfig(), r)
|
||||
client := route53.New(session.New(config))
|
||||
|
||||
return &DNSProvider{client: client}, nil
|
||||
}
|
||||
|
||||
// Present creates a TXT record using the specified parameters
|
||||
func (r *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
|
||||
value = `"` + value + `"`
|
||||
return r.changeRecord("UPSERT", fqdn, value, route53TTL)
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters
|
||||
func (r *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
|
||||
value = `"` + value + `"`
|
||||
return r.changeRecord("DELETE", fqdn, value, route53TTL)
|
||||
}
|
||||
|
||||
func (r *DNSProvider) changeRecord(action, fqdn, value string, ttl int) error {
|
||||
hostedZoneID, err := getHostedZoneID(fqdn, r.client)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to determine Route 53 hosted zone ID: %v", err)
|
||||
}
|
||||
|
||||
recordSet := newTXTRecordSet(fqdn, value, ttl)
|
||||
reqParams := &route53.ChangeResourceRecordSetsInput{
|
||||
HostedZoneId: aws.String(hostedZoneID),
|
||||
ChangeBatch: &route53.ChangeBatch{
|
||||
Comment: aws.String("Managed by Lego"),
|
||||
Changes: []*route53.Change{
|
||||
{
|
||||
Action: aws.String(action),
|
||||
ResourceRecordSet: recordSet,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
resp, err := r.client.ChangeResourceRecordSets(reqParams)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to change Route 53 record set: %v", err)
|
||||
}
|
||||
|
||||
statusID := resp.ChangeInfo.Id
|
||||
|
||||
return acme.WaitFor(120*time.Second, 4*time.Second, func() (bool, error) {
|
||||
reqParams := &route53.GetChangeInput{
|
||||
Id: statusID,
|
||||
}
|
||||
resp, err := r.client.GetChange(reqParams)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("Failed to query Route 53 change status: %v", err)
|
||||
}
|
||||
if *resp.ChangeInfo.Status == route53.ChangeStatusInsync {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
})
|
||||
}
|
||||
|
||||
func getHostedZoneID(fqdn string, client *route53.Route53) (string, error) {
|
||||
authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// .DNSName should not have a trailing dot
|
||||
reqParams := &route53.ListHostedZonesByNameInput{
|
||||
DNSName: aws.String(acme.UnFqdn(authZone)),
|
||||
}
|
||||
resp, err := client.ListHostedZonesByName(reqParams)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var hostedZoneID string
|
||||
for _, hostedZone := range resp.HostedZones {
|
||||
// .Name has a trailing dot
|
||||
if !*hostedZone.Config.PrivateZone && *hostedZone.Name == authZone {
|
||||
hostedZoneID = *hostedZone.Id
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if len(hostedZoneID) == 0 {
|
||||
return "", fmt.Errorf("Zone %s not found in Route 53 for domain %s", authZone, fqdn)
|
||||
}
|
||||
|
||||
if strings.HasPrefix(hostedZoneID, "/hostedzone/") {
|
||||
hostedZoneID = strings.TrimPrefix(hostedZoneID, "/hostedzone/")
|
||||
}
|
||||
|
||||
return hostedZoneID, nil
|
||||
}
|
||||
|
||||
func newTXTRecordSet(fqdn, value string, ttl int) *route53.ResourceRecordSet {
|
||||
return &route53.ResourceRecordSet{
|
||||
Name: aws.String(fqdn),
|
||||
Type: aws.String("TXT"),
|
||||
TTL: aws.Int64(int64(ttl)),
|
||||
ResourceRecords: []*route53.ResourceRecord{
|
||||
{Value: aws.String(value)},
|
||||
},
|
||||
}
|
||||
}
|
127
vendor/github.com/xenolf/lego/providers/dns/vultr/vultr.go
generated
vendored
Normal file
127
vendor/github.com/xenolf/lego/providers/dns/vultr/vultr.go
generated
vendored
Normal file
|
@ -0,0 +1,127 @@
|
|||
// Package vultr implements a DNS provider for solving the DNS-01 challenge using
|
||||
// the vultr DNS.
|
||||
// See https://www.vultr.com/api/#dns
|
||||
package vultr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
vultr "github.com/JamesClonk/vultr/lib"
|
||||
"github.com/xenolf/lego/acme"
|
||||
)
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface.
|
||||
type DNSProvider struct {
|
||||
client *vultr.Client
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance with a configured Vultr client.
|
||||
// Authentication uses the VULTR_API_KEY environment variable.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
apiKey := os.Getenv("VULTR_API_KEY")
|
||||
return NewDNSProviderCredentials(apiKey)
|
||||
}
|
||||
|
||||
// NewDNSProviderCredentials uses the supplied credentials to return a DNSProvider
|
||||
// instance configured for Vultr.
|
||||
func NewDNSProviderCredentials(apiKey string) (*DNSProvider, error) {
|
||||
if apiKey == "" {
|
||||
return nil, fmt.Errorf("Vultr credentials missing")
|
||||
}
|
||||
|
||||
c := &DNSProvider{
|
||||
client: vultr.NewClient(apiKey, nil),
|
||||
}
|
||||
|
||||
return c, 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)
|
||||
|
||||
zoneDomain, err := c.getHostedZone(domain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
name := c.extractRecordName(fqdn, zoneDomain)
|
||||
|
||||
err = c.client.CreateDNSRecord(zoneDomain, name, "TXT", `"`+value+`"`, 0, ttl)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Vultr API call failed: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters.
|
||||
func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
|
||||
|
||||
zoneDomain, records, err := c.findTxtRecords(domain, fqdn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, rec := range records {
|
||||
err := c.client.DeleteDNSRecord(zoneDomain, rec.RecordID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *DNSProvider) getHostedZone(domain string) (string, error) {
|
||||
domains, err := c.client.GetDNSDomains()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Vultr API call failed: %v", err)
|
||||
}
|
||||
|
||||
var hostedDomain vultr.DNSDomain
|
||||
for _, d := range domains {
|
||||
if strings.HasSuffix(domain, d.Domain) {
|
||||
if len(d.Domain) > len(hostedDomain.Domain) {
|
||||
hostedDomain = d
|
||||
}
|
||||
}
|
||||
}
|
||||
if hostedDomain.Domain == "" {
|
||||
return "", fmt.Errorf("No matching Vultr domain found for domain %s", domain)
|
||||
}
|
||||
|
||||
return hostedDomain.Domain, nil
|
||||
}
|
||||
|
||||
func (c *DNSProvider) findTxtRecords(domain, fqdn string) (string, []vultr.DNSRecord, error) {
|
||||
zoneDomain, err := c.getHostedZone(domain)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
var records []vultr.DNSRecord
|
||||
result, err := c.client.GetDNSRecords(zoneDomain)
|
||||
if err != nil {
|
||||
return "", records, fmt.Errorf("Vultr API call has failed: %v", err)
|
||||
}
|
||||
|
||||
recordName := c.extractRecordName(fqdn, zoneDomain)
|
||||
for _, record := range result {
|
||||
if record.Type == "TXT" && record.Name == recordName {
|
||||
records = append(records, record)
|
||||
}
|
||||
}
|
||||
|
||||
return zoneDomain, records, nil
|
||||
}
|
||||
|
||||
func (c *DNSProvider) extractRecordName(fqdn, domain string) string {
|
||||
name := acme.UnFqdn(fqdn)
|
||||
if idx := strings.Index(name, "."+domain); idx != -1 {
|
||||
return name[:idx]
|
||||
}
|
||||
return name
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue