Migrate to go-acme/lego.
This commit is contained in:
parent
4a68d29ce2
commit
87da7520de
286 changed files with 14021 additions and 2501 deletions
162
vendor/github.com/go-acme/lego/providers/dns/acmedns/acmedns.go
generated
vendored
Normal file
162
vendor/github.com/go-acme/lego/providers/dns/acmedns/acmedns.go
generated
vendored
Normal file
|
@ -0,0 +1,162 @@
|
|||
// Package acmedns implements a DNS provider for solving DNS-01 challenges using Joohoi's acme-dns project.
|
||||
// For more information see the ACME-DNS homepage: https://github.com/joohoi/acme-dns
|
||||
package acmedns
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/cpu/goacmedns"
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
)
|
||||
|
||||
const (
|
||||
// envNamespace is the prefix for ACME-DNS environment variables.
|
||||
envNamespace = "ACME_DNS_"
|
||||
// apiBaseEnvVar is the environment variable name for the ACME-DNS API address
|
||||
// (e.g. https://acmedns.your-domain.com).
|
||||
apiBaseEnvVar = envNamespace + "API_BASE"
|
||||
// storagePathEnvVar is the environment variable name for the ACME-DNS JSON account data file.
|
||||
// A per-domain account will be registered/persisted to this file and used for TXT updates.
|
||||
storagePathEnvVar = envNamespace + "STORAGE_PATH"
|
||||
)
|
||||
|
||||
// acmeDNSClient is an interface describing the goacmedns.Client functions the DNSProvider uses.
|
||||
// It makes it easier for tests to shim a mock Client into the DNSProvider.
|
||||
type acmeDNSClient interface {
|
||||
// UpdateTXTRecord updates the provided account's TXT record
|
||||
// to the given value or returns an error.
|
||||
UpdateTXTRecord(goacmedns.Account, string) error
|
||||
// RegisterAccount registers and returns a new account
|
||||
// with the given allowFrom restriction or returns an error.
|
||||
RegisterAccount([]string) (goacmedns.Account, error)
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface for
|
||||
// an ACME-DNS server.
|
||||
type DNSProvider struct {
|
||||
client acmeDNSClient
|
||||
storage goacmedns.Storage
|
||||
}
|
||||
|
||||
// NewDNSProvider creates an ACME-DNS provider using file based account storage.
|
||||
// Its configuration is loaded from the environment by reading apiBaseEnvVar and storagePathEnvVar.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get(apiBaseEnvVar, storagePathEnvVar)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("acme-dns: %v", err)
|
||||
}
|
||||
|
||||
client := goacmedns.NewClient(values[apiBaseEnvVar])
|
||||
storage := goacmedns.NewFileStorage(values[storagePathEnvVar], 0600)
|
||||
return NewDNSProviderClient(client, storage)
|
||||
}
|
||||
|
||||
// NewDNSProviderClient creates an ACME-DNS DNSProvider with the given acmeDNSClient and goacmedns.Storage.
|
||||
func NewDNSProviderClient(client acmeDNSClient, storage goacmedns.Storage) (*DNSProvider, error) {
|
||||
if client == nil {
|
||||
return nil, errors.New("ACME-DNS Client must be not nil")
|
||||
}
|
||||
|
||||
if storage == nil {
|
||||
return nil, errors.New("ACME-DNS Storage must be not nil")
|
||||
}
|
||||
|
||||
return &DNSProvider{
|
||||
client: client,
|
||||
storage: storage,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ErrCNAMERequired is returned by Present when the Domain indicated had no
|
||||
// existing ACME-DNS account in the Storage and additional setup is required.
|
||||
// The user must create a CNAME in the DNS zone for Domain that aliases FQDN
|
||||
// to Target in order to complete setup for the ACME-DNS account that was created.
|
||||
type ErrCNAMERequired struct {
|
||||
// The Domain that is being issued for.
|
||||
Domain string
|
||||
// The alias of the CNAME (left hand DNS label).
|
||||
FQDN string
|
||||
// The RDATA of the CNAME (right hand side, canonical name).
|
||||
Target string
|
||||
}
|
||||
|
||||
// Error returns a descriptive message for the ErrCNAMERequired instance telling
|
||||
// the user that a CNAME needs to be added to the DNS zone of c.Domain before
|
||||
// the ACME-DNS hook will work. The CNAME to be created should be of the form:
|
||||
// {{ c.FQDN }} CNAME {{ c.Target }}
|
||||
func (e ErrCNAMERequired) Error() string {
|
||||
return fmt.Sprintf("acme-dns: new account created for %q. "+
|
||||
"To complete setup for %q you must provision the following "+
|
||||
"CNAME in your DNS zone and re-run this provider when it is "+
|
||||
"in place:\n"+
|
||||
"%s CNAME %s.",
|
||||
e.Domain, e.Domain, e.FQDN, e.Target)
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfill the DNS-01 challenge.
|
||||
// If there is an existing account for the domain in the provider's storage
|
||||
// then it will be used to set the challenge response TXT record with the ACME-DNS server and issuance will continue.
|
||||
// If there is not an account for the given domain present in the DNSProvider storage
|
||||
// one will be created and registered with the ACME DNS server and an ErrCNAMERequired error is returned.
|
||||
// This will halt issuance and indicate to the user that a one-time manual setup is required for the domain.
|
||||
func (d *DNSProvider) Present(domain, _, keyAuth string) error {
|
||||
// Compute the challenge response FQDN and TXT value for the domain based
|
||||
// on the keyAuth.
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
// Check if credentials were previously saved for this domain.
|
||||
account, err := d.storage.Fetch(domain)
|
||||
// Errors other than goacmeDNS.ErrDomainNotFound are unexpected.
|
||||
if err != nil && err != goacmedns.ErrDomainNotFound {
|
||||
return err
|
||||
}
|
||||
if err == goacmedns.ErrDomainNotFound {
|
||||
// The account did not exist. Create a new one and return an error
|
||||
// indicating the required one-time manual CNAME setup.
|
||||
return d.register(domain, fqdn)
|
||||
}
|
||||
|
||||
// Update the acme-dns TXT record.
|
||||
return d.client.UpdateTXTRecord(account, value)
|
||||
}
|
||||
|
||||
// CleanUp removes the record matching the specified parameters. It is not
|
||||
// implemented for the ACME-DNS provider.
|
||||
func (d *DNSProvider) CleanUp(_, _, _ string) error {
|
||||
// ACME-DNS doesn't support the notion of removing a record.
|
||||
// For users of ACME-DNS it is expected the stale records remain in-place.
|
||||
return nil
|
||||
}
|
||||
|
||||
// register creates a new ACME-DNS account for the given domain.
|
||||
// If account creation works as expected a ErrCNAMERequired error is returned describing
|
||||
// the one-time manual CNAME setup required to complete setup of the ACME-DNS hook for the domain.
|
||||
// If any other error occurs it is returned as-is.
|
||||
func (d *DNSProvider) register(domain, fqdn string) error {
|
||||
// TODO(@cpu): Read CIDR whitelists from the environment
|
||||
newAcct, err := d.client.RegisterAccount(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Store the new account in the storage and call save to persist the data.
|
||||
err = d.storage.Put(domain, newAcct)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = d.storage.Save()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Stop issuance by returning an error.
|
||||
// The user needs to perform a manual one-time CNAME setup in their DNS zone
|
||||
// to complete the setup of the new account we created.
|
||||
return ErrCNAMERequired{
|
||||
Domain: domain,
|
||||
FQDN: fqdn,
|
||||
Target: newAcct.FullDomain,
|
||||
}
|
||||
}
|
220
vendor/github.com/go-acme/lego/providers/dns/alidns/alidns.go
generated
vendored
Normal file
220
vendor/github.com/go-acme/lego/providers/dns/alidns/alidns.go
generated
vendored
Normal file
|
@ -0,0 +1,220 @@
|
|||
// Package alidns implements a DNS provider for solving the DNS-01 challenge using Alibaba Cloud DNS.
|
||||
package alidns
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk"
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/credentials"
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/services/alidns"
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
)
|
||||
|
||||
const defaultRegionID = "cn-hangzhou"
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
APIKey string
|
||||
SecretKey string
|
||||
RegionID string
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
HTTPTimeout time.Duration
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt("ALICLOUD_TTL", 600),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("ALICLOUD_PROPAGATION_TIMEOUT", dns01.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("ALICLOUD_POLLING_INTERVAL", dns01.DefaultPollingInterval),
|
||||
HTTPTimeout: env.GetOrDefaultSecond("ALICLOUD_HTTP_TIMEOUT", 10*time.Second),
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
client *alidns.Client
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for Alibaba Cloud DNS.
|
||||
// Credentials must be passed in the environment variables: ALICLOUD_ACCESS_KEY and ALICLOUD_SECRET_KEY.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("ALICLOUD_ACCESS_KEY", "ALICLOUD_SECRET_KEY")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("alicloud: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.APIKey = values["ALICLOUD_ACCESS_KEY"]
|
||||
config.SecretKey = values["ALICLOUD_SECRET_KEY"]
|
||||
config.RegionID = env.GetOrFile("ALICLOUD_REGION_ID")
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for alidns.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("alicloud: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if config.APIKey == "" || config.SecretKey == "" {
|
||||
return nil, fmt.Errorf("alicloud: credentials missing")
|
||||
}
|
||||
|
||||
if len(config.RegionID) == 0 {
|
||||
config.RegionID = defaultRegionID
|
||||
}
|
||||
|
||||
conf := sdk.NewConfig().WithTimeout(config.HTTPTimeout)
|
||||
credential := credentials.NewAccessKeyCredential(config.APIKey, config.SecretKey)
|
||||
|
||||
client, err := alidns.NewClientWithOptions(config.RegionID, conf, credential)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("alicloud: credentials failed: %v", err)
|
||||
}
|
||||
|
||||
return &DNSProvider{config: config, client: client}, nil
|
||||
}
|
||||
|
||||
// Timeout returns the timeout and interval to use when checking for DNS propagation.
|
||||
// Adjusting here to cope with spikes in propagation times.
|
||||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge.
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
_, zoneName, err := d.getHostedZone(domain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("alicloud: %v", err)
|
||||
}
|
||||
|
||||
recordAttributes := d.newTxtRecord(zoneName, fqdn, value)
|
||||
|
||||
_, err = d.client.AddDomainRecord(recordAttributes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("alicloud: API call failed: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters.
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
records, err := d.findTxtRecords(domain, fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("alicloud: %v", err)
|
||||
}
|
||||
|
||||
_, _, err = d.getHostedZone(domain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("alicloud: %v", err)
|
||||
}
|
||||
|
||||
for _, rec := range records {
|
||||
request := alidns.CreateDeleteDomainRecordRequest()
|
||||
request.RecordId = rec.RecordId
|
||||
_, err = d.client.DeleteDomainRecord(request)
|
||||
if err != nil {
|
||||
return fmt.Errorf("alicloud: %v", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) getHostedZone(domain string) (string, string, error) {
|
||||
request := alidns.CreateDescribeDomainsRequest()
|
||||
|
||||
var domains []alidns.Domain
|
||||
startPage := 1
|
||||
|
||||
for {
|
||||
request.PageNumber = requests.NewInteger(startPage)
|
||||
|
||||
response, err := d.client.DescribeDomains(request)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("API call failed: %v", err)
|
||||
}
|
||||
|
||||
domains = append(domains, response.Domains.Domain...)
|
||||
|
||||
if response.PageNumber*response.PageSize >= response.TotalCount {
|
||||
break
|
||||
}
|
||||
|
||||
startPage++
|
||||
}
|
||||
|
||||
authZone, err := dns01.FindZoneByFqdn(dns01.ToFqdn(domain))
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
var hostedZone alidns.Domain
|
||||
for _, zone := range domains {
|
||||
if zone.DomainName == dns01.UnFqdn(authZone) {
|
||||
hostedZone = zone
|
||||
}
|
||||
}
|
||||
|
||||
if hostedZone.DomainId == "" {
|
||||
return "", "", fmt.Errorf("zone %s not found in AliDNS for domain %s", authZone, domain)
|
||||
}
|
||||
return fmt.Sprintf("%v", hostedZone.DomainId), hostedZone.DomainName, nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) newTxtRecord(zone, fqdn, value string) *alidns.AddDomainRecordRequest {
|
||||
request := alidns.CreateAddDomainRecordRequest()
|
||||
request.Type = "TXT"
|
||||
request.DomainName = zone
|
||||
request.RR = d.extractRecordName(fqdn, zone)
|
||||
request.Value = value
|
||||
request.TTL = requests.NewInteger(d.config.TTL)
|
||||
return request
|
||||
}
|
||||
|
||||
func (d *DNSProvider) findTxtRecords(domain, fqdn string) ([]alidns.Record, error) {
|
||||
_, zoneName, err := d.getHostedZone(domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
request := alidns.CreateDescribeDomainRecordsRequest()
|
||||
request.DomainName = zoneName
|
||||
request.PageSize = requests.NewInteger(500)
|
||||
|
||||
var records []alidns.Record
|
||||
|
||||
result, err := d.client.DescribeDomainRecords(request)
|
||||
if err != nil {
|
||||
return records, fmt.Errorf("API call has failed: %v", err)
|
||||
}
|
||||
|
||||
recordName := d.extractRecordName(fqdn, zoneName)
|
||||
for _, record := range result.DomainRecords.Record {
|
||||
if record.RR == recordName {
|
||||
records = append(records, record)
|
||||
}
|
||||
}
|
||||
return records, nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) extractRecordName(fqdn, domain string) string {
|
||||
name := dns01.UnFqdn(fqdn)
|
||||
if idx := strings.Index(name, "."+domain); idx != -1 {
|
||||
return name[:idx]
|
||||
}
|
||||
return name
|
||||
}
|
191
vendor/github.com/go-acme/lego/providers/dns/auroradns/auroradns.go
generated
vendored
Normal file
191
vendor/github.com/go-acme/lego/providers/dns/auroradns/auroradns.go
generated
vendored
Normal file
|
@ -0,0 +1,191 @@
|
|||
// Package auroradns implements a DNS provider for solving the DNS-01 challenge using Aurora DNS.
|
||||
package auroradns
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
"github.com/nrdcg/auroradns"
|
||||
)
|
||||
|
||||
const defaultBaseURL = "https://api.auroradns.eu"
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
BaseURL string
|
||||
UserID string
|
||||
Key string
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt("AURORA_TTL", 300),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("AURORA_PROPAGATION_TIMEOUT", dns01.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("AURORA_POLLING_INTERVAL", dns01.DefaultPollingInterval),
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider describes a provider for AuroraDNS
|
||||
type DNSProvider struct {
|
||||
recordIDs map[string]string
|
||||
recordIDsMu sync.Mutex
|
||||
config *Config
|
||||
client *auroradns.Client
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for AuroraDNS.
|
||||
// Credentials must be passed in the environment variables:
|
||||
// AURORA_USER_ID and AURORA_KEY.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("AURORA_USER_ID", "AURORA_KEY")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("aurora: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.BaseURL = env.GetOrFile("AURORA_ENDPOINT")
|
||||
config.UserID = values["AURORA_USER_ID"]
|
||||
config.Key = values["AURORA_KEY"]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for AuroraDNS.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("aurora: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if config.UserID == "" || config.Key == "" {
|
||||
return nil, errors.New("aurora: some credentials information are missing")
|
||||
}
|
||||
|
||||
if config.BaseURL == "" {
|
||||
config.BaseURL = defaultBaseURL
|
||||
}
|
||||
|
||||
tr, err := auroradns.NewTokenTransport(config.UserID, config.Key)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("aurora: %v", err)
|
||||
}
|
||||
|
||||
client, err := auroradns.NewClient(tr.Client(), auroradns.WithBaseURL(config.BaseURL))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("aurora: %v", err)
|
||||
}
|
||||
|
||||
return &DNSProvider{
|
||||
config: config,
|
||||
client: client,
|
||||
recordIDs: make(map[string]string),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Present creates a record with a secret
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
authZone, err := dns01.FindZoneByFqdn(dns01.ToFqdn(domain))
|
||||
if err != nil {
|
||||
return fmt.Errorf("aurora: could not determine zone for domain: '%s'. %s", domain, err)
|
||||
}
|
||||
|
||||
// 1. Aurora will happily create the TXT record when it is provided a fqdn,
|
||||
// but it will only appear in the control panel and will not be
|
||||
// propagated to DNS servers. Extract and use subdomain instead.
|
||||
// 2. A trailing dot in the fqdn will cause Aurora to add a trailing dot to
|
||||
// the subdomain, resulting in _acme-challenge..<domain> rather
|
||||
// than _acme-challenge.<domain>
|
||||
|
||||
subdomain := fqdn[0 : len(fqdn)-len(authZone)-1]
|
||||
|
||||
authZone = dns01.UnFqdn(authZone)
|
||||
|
||||
zone, err := d.getZoneInformationByName(authZone)
|
||||
if err != nil {
|
||||
return fmt.Errorf("aurora: could not create record: %v", err)
|
||||
}
|
||||
|
||||
record := auroradns.Record{
|
||||
RecordType: "TXT",
|
||||
Name: subdomain,
|
||||
Content: value,
|
||||
TTL: d.config.TTL,
|
||||
}
|
||||
|
||||
newRecord, _, err := d.client.CreateRecord(zone.ID, record)
|
||||
if err != nil {
|
||||
return fmt.Errorf("aurora: could not create record: %v", err)
|
||||
}
|
||||
|
||||
d.recordIDsMu.Lock()
|
||||
d.recordIDs[fqdn] = newRecord.ID
|
||||
d.recordIDsMu.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes a given record that was generated by Present
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
d.recordIDsMu.Lock()
|
||||
recordID, ok := d.recordIDs[fqdn]
|
||||
d.recordIDsMu.Unlock()
|
||||
|
||||
if !ok {
|
||||
return fmt.Errorf("unknown recordID for %q", fqdn)
|
||||
}
|
||||
|
||||
authZone, err := dns01.FindZoneByFqdn(dns01.ToFqdn(domain))
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not determine zone for domain: %q. %v", domain, err)
|
||||
}
|
||||
|
||||
authZone = dns01.UnFqdn(authZone)
|
||||
|
||||
zone, err := d.getZoneInformationByName(authZone)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, _, err = d.client.DeleteRecord(zone.ID, recordID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.recordIDsMu.Lock()
|
||||
delete(d.recordIDs, fqdn)
|
||||
d.recordIDsMu.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Timeout returns the timeout and interval to use when checking for DNS propagation.
|
||||
// Adjusting here to cope with spikes in propagation times.
|
||||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
func (d *DNSProvider) getZoneInformationByName(name string) (auroradns.Zone, error) {
|
||||
zs, _, err := d.client.ListZones()
|
||||
if err != nil {
|
||||
return auroradns.Zone{}, err
|
||||
}
|
||||
|
||||
for _, element := range zs {
|
||||
if element.Name == name {
|
||||
return element, nil
|
||||
}
|
||||
}
|
||||
|
||||
return auroradns.Zone{}, fmt.Errorf("could not find Zone record")
|
||||
}
|
274
vendor/github.com/go-acme/lego/providers/dns/azure/azure.go
generated
vendored
Normal file
274
vendor/github.com/go-acme/lego/providers/dns/azure/azure.go
generated
vendored
Normal file
|
@ -0,0 +1,274 @@
|
|||
// Package azure implements a DNS provider for solving the DNS-01 challenge using azure DNS.
|
||||
// Azure doesn't like trailing dots on domain names, most of the acme code does.
|
||||
package azure
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/services/dns/mgmt/2017-09-01/dns"
|
||||
"github.com/Azure/go-autorest/autorest"
|
||||
"github.com/Azure/go-autorest/autorest/adal"
|
||||
"github.com/Azure/go-autorest/autorest/azure"
|
||||
"github.com/Azure/go-autorest/autorest/azure/auth"
|
||||
"github.com/Azure/go-autorest/autorest/to"
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
)
|
||||
|
||||
const defaultMetadataEndpoint = "http://169.254.169.254"
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
// optional if using instance metadata service
|
||||
ClientID string
|
||||
ClientSecret string
|
||||
TenantID string
|
||||
|
||||
SubscriptionID string
|
||||
ResourceGroup string
|
||||
|
||||
MetadataEndpoint string
|
||||
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt("AZURE_TTL", 60),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("AZURE_PROPAGATION_TIMEOUT", 2*time.Minute),
|
||||
PollingInterval: env.GetOrDefaultSecond("AZURE_POLLING_INTERVAL", 2*time.Second),
|
||||
MetadataEndpoint: env.GetOrFile("AZURE_METADATA_ENDPOINT"),
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
authorizer autorest.Authorizer
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for azure.
|
||||
// Credentials can be passed in the environment variables:
|
||||
// AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, AZURE_SUBSCRIPTION_ID, AZURE_TENANT_ID, AZURE_RESOURCE_GROUP
|
||||
// If the credentials are _not_ set via the environment,
|
||||
// then it will attempt to get a bearer token via the instance metadata service.
|
||||
// see: https://github.com/Azure/go-autorest/blob/v10.14.0/autorest/azure/auth/auth.go#L38-L42
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
config := NewDefaultConfig()
|
||||
config.SubscriptionID = env.GetOrFile("AZURE_SUBSCRIPTION_ID")
|
||||
config.ResourceGroup = env.GetOrFile("AZURE_RESOURCE_GROUP")
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for Azure.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("azure: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if config.HTTPClient == nil {
|
||||
config.HTTPClient = http.DefaultClient
|
||||
}
|
||||
|
||||
authorizer, err := getAuthorizer(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if config.SubscriptionID == "" {
|
||||
subsID, err := getMetadata(config, "subscriptionId")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("azure: %v", err)
|
||||
}
|
||||
|
||||
if subsID == "" {
|
||||
return nil, errors.New("azure: SubscriptionID is missing")
|
||||
}
|
||||
config.SubscriptionID = subsID
|
||||
}
|
||||
|
||||
if config.ResourceGroup == "" {
|
||||
resGroup, err := getMetadata(config, "resourceGroupName")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("azure: %v", err)
|
||||
}
|
||||
|
||||
if resGroup == "" {
|
||||
return nil, errors.New("azure: ResourceGroup is missing")
|
||||
}
|
||||
config.ResourceGroup = resGroup
|
||||
}
|
||||
|
||||
return &DNSProvider{config: config, authorizer: authorizer}, nil
|
||||
}
|
||||
|
||||
// Timeout returns the timeout and interval to use when checking for DNS propagation.
|
||||
// Adjusting here to cope with spikes in propagation times.
|
||||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
ctx := context.Background()
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
zone, err := d.getHostedZoneID(ctx, fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("azure: %v", err)
|
||||
}
|
||||
|
||||
rsc := dns.NewRecordSetsClient(d.config.SubscriptionID)
|
||||
rsc.Authorizer = d.authorizer
|
||||
|
||||
relative := toRelativeRecord(fqdn, dns01.ToFqdn(zone))
|
||||
|
||||
// Get existing record set
|
||||
rset, err := rsc.Get(ctx, d.config.ResourceGroup, zone, relative, dns.TXT)
|
||||
if err != nil {
|
||||
detailedError, ok := err.(autorest.DetailedError)
|
||||
if !ok || detailedError.StatusCode != http.StatusNotFound {
|
||||
return fmt.Errorf("azure: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Construct unique TXT records using map
|
||||
uniqRecords := map[string]struct{}{value: {}}
|
||||
if rset.RecordSetProperties != nil && rset.TxtRecords != nil {
|
||||
for _, txtRecord := range *rset.TxtRecords {
|
||||
// Assume Value doesn't contain multiple strings
|
||||
if txtRecord.Value != nil && len(*txtRecord.Value) > 0 {
|
||||
uniqRecords[(*txtRecord.Value)[0]] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var txtRecords []dns.TxtRecord
|
||||
for txt := range uniqRecords {
|
||||
txtRecords = append(txtRecords, dns.TxtRecord{Value: &[]string{txt}})
|
||||
}
|
||||
|
||||
rec := dns.RecordSet{
|
||||
Name: &relative,
|
||||
RecordSetProperties: &dns.RecordSetProperties{
|
||||
TTL: to.Int64Ptr(int64(d.config.TTL)),
|
||||
TxtRecords: &txtRecords,
|
||||
},
|
||||
}
|
||||
|
||||
_, err = rsc.CreateOrUpdate(ctx, d.config.ResourceGroup, zone, relative, dns.TXT, rec, "", "")
|
||||
if err != nil {
|
||||
return fmt.Errorf("azure: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
ctx := context.Background()
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
zone, err := d.getHostedZoneID(ctx, fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("azure: %v", err)
|
||||
}
|
||||
|
||||
relative := toRelativeRecord(fqdn, dns01.ToFqdn(zone))
|
||||
rsc := dns.NewRecordSetsClient(d.config.SubscriptionID)
|
||||
rsc.Authorizer = d.authorizer
|
||||
|
||||
_, err = rsc.Delete(ctx, d.config.ResourceGroup, zone, relative, dns.TXT, "")
|
||||
if err != nil {
|
||||
return fmt.Errorf("azure: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Checks that azure has a zone for this domain name.
|
||||
func (d *DNSProvider) getHostedZoneID(ctx context.Context, fqdn string) (string, error) {
|
||||
authZone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
dc := dns.NewZonesClient(d.config.SubscriptionID)
|
||||
dc.Authorizer = d.authorizer
|
||||
|
||||
zone, err := dc.Get(ctx, d.config.ResourceGroup, dns01.UnFqdn(authZone))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// zone.Name shouldn't have a trailing dot(.)
|
||||
return to.String(zone.Name), nil
|
||||
}
|
||||
|
||||
// Returns the relative record to the domain
|
||||
func toRelativeRecord(domain, zone string) string {
|
||||
return dns01.UnFqdn(strings.TrimSuffix(domain, zone))
|
||||
}
|
||||
|
||||
func getAuthorizer(config *Config) (autorest.Authorizer, error) {
|
||||
if config.ClientID != "" && config.ClientSecret != "" && config.TenantID != "" {
|
||||
oauthConfig, err := adal.NewOAuthConfig(azure.PublicCloud.ActiveDirectoryEndpoint, config.TenantID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
spt, err := adal.NewServicePrincipalToken(*oauthConfig, config.ClientID, config.ClientSecret, azure.PublicCloud.ResourceManagerEndpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
spt.SetSender(config.HTTPClient)
|
||||
return autorest.NewBearerAuthorizer(spt), nil
|
||||
}
|
||||
|
||||
return auth.NewAuthorizerFromEnvironment()
|
||||
}
|
||||
|
||||
// Fetches metadata from environment or he instance metadata service
|
||||
// borrowed from https://github.com/Microsoft/azureimds/blob/master/imdssample.go
|
||||
func getMetadata(config *Config, field string) (string, error) {
|
||||
metadataEndpoint := config.MetadataEndpoint
|
||||
if len(metadataEndpoint) == 0 {
|
||||
metadataEndpoint = defaultMetadataEndpoint
|
||||
}
|
||||
|
||||
resource := fmt.Sprintf("%s/metadata/instance/compute/%s", metadataEndpoint, field)
|
||||
req, err := http.NewRequest(http.MethodGet, resource, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
req.Header.Add("Metadata", "True")
|
||||
|
||||
q := req.URL.Query()
|
||||
q.Add("format", "text")
|
||||
q.Add("api-version", "2017-12-01")
|
||||
req.URL.RawQuery = q.Encode()
|
||||
|
||||
resp, err := config.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
respBody, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(respBody), nil
|
||||
}
|
201
vendor/github.com/go-acme/lego/providers/dns/bluecat/bluecat.go
generated
vendored
Normal file
201
vendor/github.com/go-acme/lego/providers/dns/bluecat/bluecat.go
generated
vendored
Normal file
|
@ -0,0 +1,201 @@
|
|||
// Package bluecat implements a DNS provider for solving the DNS-01 challenge using a self-hosted Bluecat Address Manager.
|
||||
package bluecat
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
)
|
||||
|
||||
const (
|
||||
configType = "Configuration"
|
||||
viewType = "View"
|
||||
zoneType = "Zone"
|
||||
txtType = "TXTRecord"
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
BaseURL string
|
||||
UserName string
|
||||
Password string
|
||||
ConfigName string
|
||||
DNSView string
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt("BLUECAT_TTL", dns01.DefaultTTL),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("BLUECAT_PROPAGATION_TIMEOUT", dns01.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("BLUECAT_POLLING_INTERVAL", dns01.DefaultPollingInterval),
|
||||
HTTPClient: &http.Client{
|
||||
Timeout: env.GetOrDefaultSecond("BLUECAT_HTTP_TIMEOUT", 30*time.Second),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface that uses
|
||||
// Bluecat's Address Manager REST API to manage TXT records for a domain.
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
token string
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for Bluecat DNS.
|
||||
// Credentials must be passed in the environment variables: BLUECAT_SERVER_URL, BLUECAT_USER_NAME and BLUECAT_PASSWORD.
|
||||
// BLUECAT_SERVER_URL should have the scheme, hostname, and port (if required) of the authoritative Bluecat BAM server.
|
||||
// The REST endpoint will be appended.
|
||||
// In addition, the Configuration name and external DNS View Name must be passed in BLUECAT_CONFIG_NAME and BLUECAT_DNS_VIEW
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("BLUECAT_SERVER_URL", "BLUECAT_USER_NAME", "BLUECAT_PASSWORD", "BLUECAT_CONFIG_NAME", "BLUECAT_DNS_VIEW")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("bluecat: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.BaseURL = values["BLUECAT_SERVER_URL"]
|
||||
config.UserName = values["BLUECAT_USER_NAME"]
|
||||
config.Password = values["BLUECAT_PASSWORD"]
|
||||
config.ConfigName = values["BLUECAT_CONFIG_NAME"]
|
||||
config.DNSView = values["BLUECAT_DNS_VIEW"]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for Bluecat DNS.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("bluecat: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if config.BaseURL == "" || config.UserName == "" || config.Password == "" || config.ConfigName == "" || config.DNSView == "" {
|
||||
return nil, fmt.Errorf("bluecat: credentials missing")
|
||||
}
|
||||
|
||||
return &DNSProvider{config: config}, nil
|
||||
}
|
||||
|
||||
// Present creates a TXT record using the specified parameters
|
||||
// This will *not* create a subzone to contain the TXT record,
|
||||
// so make sure the FQDN specified is within an extant zone.
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
err := d.login()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
viewID, err := d.lookupViewID(d.config.DNSView)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
parentZoneID, name, err := d.lookupParentZoneID(viewID, fqdn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
queryArgs := map[string]string{
|
||||
"parentId": strconv.FormatUint(uint64(parentZoneID), 10),
|
||||
}
|
||||
|
||||
body := bluecatEntity{
|
||||
Name: name,
|
||||
Type: "TXTRecord",
|
||||
Properties: fmt.Sprintf("ttl=%d|absoluteName=%s|txt=%s|", d.config.TTL, fqdn, value),
|
||||
}
|
||||
|
||||
resp, err := d.sendRequest(http.MethodPost, "addEntity", body, queryArgs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
addTxtBytes, _ := ioutil.ReadAll(resp.Body)
|
||||
addTxtResp := string(addTxtBytes)
|
||||
// addEntity responds only with body text containing the ID of the created record
|
||||
_, err = strconv.ParseUint(addTxtResp, 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("bluecat: addEntity request failed: %s", addTxtResp)
|
||||
}
|
||||
|
||||
err = d.deploy(parentZoneID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return d.logout()
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
err := d.login()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
viewID, err := d.lookupViewID(d.config.DNSView)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
parentID, name, err := d.lookupParentZoneID(viewID, fqdn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
queryArgs := map[string]string{
|
||||
"parentId": strconv.FormatUint(uint64(parentID), 10),
|
||||
"name": name,
|
||||
"type": txtType,
|
||||
}
|
||||
|
||||
resp, err := d.sendRequest(http.MethodGet, "getEntityByName", nil, queryArgs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var txtRec entityResponse
|
||||
err = json.NewDecoder(resp.Body).Decode(&txtRec)
|
||||
if err != nil {
|
||||
return fmt.Errorf("bluecat: %v", err)
|
||||
}
|
||||
queryArgs = map[string]string{
|
||||
"objectId": strconv.FormatUint(uint64(txtRec.ID), 10),
|
||||
}
|
||||
|
||||
resp, err = d.sendRequest(http.MethodDelete, http.MethodDelete, nil, queryArgs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
err = d.deploy(parentID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return d.logout()
|
||||
}
|
||||
|
||||
// Timeout returns the timeout and interval to use when checking for DNS propagation.
|
||||
// Adjusting here to cope with spikes in propagation times.
|
||||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
249
vendor/github.com/go-acme/lego/providers/dns/bluecat/client.go
generated
vendored
Normal file
249
vendor/github.com/go-acme/lego/providers/dns/bluecat/client.go
generated
vendored
Normal file
|
@ -0,0 +1,249 @@
|
|||
package bluecat
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// JSON body for Bluecat entity requests and responses
|
||||
type bluecatEntity struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Properties string `json:"properties"`
|
||||
}
|
||||
|
||||
type entityResponse struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Properties string `json:"properties"`
|
||||
}
|
||||
|
||||
// Starts a new Bluecat API Session. Authenticates using customerName, userName,
|
||||
// password and receives a token to be used in for subsequent requests.
|
||||
func (d *DNSProvider) login() error {
|
||||
queryArgs := map[string]string{
|
||||
"username": d.config.UserName,
|
||||
"password": d.config.Password,
|
||||
}
|
||||
|
||||
resp, err := d.sendRequest(http.MethodGet, "login", nil, queryArgs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
authBytes, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("bluecat: %v", err)
|
||||
}
|
||||
authResp := string(authBytes)
|
||||
|
||||
if strings.Contains(authResp, "Authentication Error") {
|
||||
msg := strings.Trim(authResp, "\"")
|
||||
return fmt.Errorf("bluecat: request failed: %s", msg)
|
||||
}
|
||||
|
||||
// Upon success, API responds with "Session Token-> BAMAuthToken: dQfuRMTUxNjc3MjcyNDg1ODppcGFybXM= <- for User : username"
|
||||
d.token = regexp.MustCompile("BAMAuthToken: [^ ]+").FindString(authResp)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Destroys Bluecat Session
|
||||
func (d *DNSProvider) logout() error {
|
||||
if len(d.token) == 0 {
|
||||
// nothing to do
|
||||
return nil
|
||||
}
|
||||
|
||||
resp, err := d.sendRequest(http.MethodGet, "logout", nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return fmt.Errorf("bluecat: request failed to delete session with HTTP status code %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
authBytes, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
authResp := string(authBytes)
|
||||
|
||||
if !strings.Contains(authResp, "successfully") {
|
||||
msg := strings.Trim(authResp, "\"")
|
||||
return fmt.Errorf("bluecat: request failed to delete session: %s", msg)
|
||||
}
|
||||
|
||||
d.token = ""
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Lookup the entity ID of the configuration named in our properties
|
||||
func (d *DNSProvider) lookupConfID() (uint, error) {
|
||||
queryArgs := map[string]string{
|
||||
"parentId": strconv.Itoa(0),
|
||||
"name": d.config.ConfigName,
|
||||
"type": configType,
|
||||
}
|
||||
|
||||
resp, err := d.sendRequest(http.MethodGet, "getEntityByName", nil, queryArgs)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var conf entityResponse
|
||||
err = json.NewDecoder(resp.Body).Decode(&conf)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("bluecat: %v", err)
|
||||
}
|
||||
return conf.ID, nil
|
||||
}
|
||||
|
||||
// Find the DNS view with the given name within
|
||||
func (d *DNSProvider) lookupViewID(viewName string) (uint, error) {
|
||||
confID, err := d.lookupConfID()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
queryArgs := map[string]string{
|
||||
"parentId": strconv.FormatUint(uint64(confID), 10),
|
||||
"name": viewName,
|
||||
"type": viewType,
|
||||
}
|
||||
|
||||
resp, err := d.sendRequest(http.MethodGet, "getEntityByName", nil, queryArgs)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var view entityResponse
|
||||
err = json.NewDecoder(resp.Body).Decode(&view)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("bluecat: %v", err)
|
||||
}
|
||||
|
||||
return view.ID, nil
|
||||
}
|
||||
|
||||
// Return the entityId of the parent zone by recursing from the root view
|
||||
// Also return the simple name of the host
|
||||
func (d *DNSProvider) lookupParentZoneID(viewID uint, fqdn string) (uint, string, error) {
|
||||
parentViewID := viewID
|
||||
name := ""
|
||||
|
||||
if fqdn != "" {
|
||||
zones := strings.Split(strings.Trim(fqdn, "."), ".")
|
||||
last := len(zones) - 1
|
||||
name = zones[0]
|
||||
|
||||
for i := last; i > -1; i-- {
|
||||
zoneID, err := d.getZone(parentViewID, zones[i])
|
||||
if err != nil || zoneID == 0 {
|
||||
return parentViewID, name, err
|
||||
}
|
||||
if i > 0 {
|
||||
name = strings.Join(zones[0:i], ".")
|
||||
}
|
||||
parentViewID = zoneID
|
||||
}
|
||||
}
|
||||
|
||||
return parentViewID, name, nil
|
||||
}
|
||||
|
||||
// Get the DNS zone with the specified name under the parentId
|
||||
func (d *DNSProvider) getZone(parentID uint, name string) (uint, error) {
|
||||
queryArgs := map[string]string{
|
||||
"parentId": strconv.FormatUint(uint64(parentID), 10),
|
||||
"name": name,
|
||||
"type": zoneType,
|
||||
}
|
||||
|
||||
resp, err := d.sendRequest(http.MethodGet, "getEntityByName", nil, queryArgs)
|
||||
|
||||
// Return an empty zone if the named zone doesn't exist
|
||||
if resp != nil && resp.StatusCode == http.StatusNotFound {
|
||||
return 0, fmt.Errorf("bluecat: could not find zone named %s", name)
|
||||
}
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var zone entityResponse
|
||||
err = json.NewDecoder(resp.Body).Decode(&zone)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("bluecat: %v", err)
|
||||
}
|
||||
|
||||
return zone.ID, nil
|
||||
}
|
||||
|
||||
// Deploy the DNS config for the specified entity to the authoritative servers
|
||||
func (d *DNSProvider) deploy(entityID uint) error {
|
||||
queryArgs := map[string]string{
|
||||
"entityId": strconv.FormatUint(uint64(entityID), 10),
|
||||
}
|
||||
|
||||
resp, err := d.sendRequest(http.MethodPost, "quickDeploy", nil, queryArgs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Send a REST request, using query parameters specified. The Authorization
|
||||
// header will be set if we have an active auth token
|
||||
func (d *DNSProvider) sendRequest(method, resource string, payload interface{}, queryArgs map[string]string) (*http.Response, error) {
|
||||
url := fmt.Sprintf("%s/Services/REST/v1/%s", d.config.BaseURL, resource)
|
||||
|
||||
body, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("bluecat: %v", err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, url, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("bluecat: %v", err)
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
if len(d.token) > 0 {
|
||||
req.Header.Set("Authorization", d.token)
|
||||
}
|
||||
|
||||
// Add all query parameters
|
||||
q := req.URL.Query()
|
||||
for argName, argVal := range queryArgs {
|
||||
q.Add(argName, argVal)
|
||||
}
|
||||
req.URL.RawQuery = q.Encode()
|
||||
resp, err := d.config.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("bluecat: %v", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode >= 400 {
|
||||
errBytes, _ := ioutil.ReadAll(resp.Body)
|
||||
errResp := string(errBytes)
|
||||
return nil, fmt.Errorf("bluecat: request failed with HTTP status code %d\n Full message: %s",
|
||||
resp.StatusCode, errResp)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
157
vendor/github.com/go-acme/lego/providers/dns/cloudflare/cloudflare.go
generated
vendored
Normal file
157
vendor/github.com/go-acme/lego/providers/dns/cloudflare/cloudflare.go
generated
vendored
Normal file
|
@ -0,0 +1,157 @@
|
|||
// Package cloudflare implements a DNS provider for solving the DNS-01 challenge using cloudflare DNS.
|
||||
package cloudflare
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
cloudflare "github.com/cloudflare/cloudflare-go"
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/log"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
)
|
||||
|
||||
const (
|
||||
minTTL = 120
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
AuthEmail string
|
||||
AuthKey string
|
||||
TTL int
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt("CLOUDFLARE_TTL", minTTL),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("CLOUDFLARE_PROPAGATION_TIMEOUT", 2*time.Minute),
|
||||
PollingInterval: env.GetOrDefaultSecond("CLOUDFLARE_POLLING_INTERVAL", 2*time.Second),
|
||||
HTTPClient: &http.Client{
|
||||
Timeout: env.GetOrDefaultSecond("CLOUDFLARE_HTTP_TIMEOUT", 30*time.Second),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface
|
||||
type DNSProvider struct {
|
||||
client *cloudflare.API
|
||||
config *Config
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for Cloudflare.
|
||||
// Credentials must be passed in the environment variables:
|
||||
// CLOUDFLARE_EMAIL and CLOUDFLARE_API_KEY.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.GetWithFallback(
|
||||
[]string{"CLOUDFLARE_EMAIL", "CF_API_EMAIL"},
|
||||
[]string{"CLOUDFLARE_API_KEY", "CF_API_KEY"})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cloudflare: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.AuthEmail = values["CLOUDFLARE_EMAIL"]
|
||||
config.AuthKey = values["CLOUDFLARE_API_KEY"]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for Cloudflare.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("cloudflare: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if config.TTL < minTTL {
|
||||
return nil, fmt.Errorf("cloudflare: invalid TTL, TTL (%d) must be greater than %d", config.TTL, minTTL)
|
||||
}
|
||||
|
||||
client, err := cloudflare.New(config.AuthKey, config.AuthEmail, cloudflare.HTTPClient(config.HTTPClient))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &DNSProvider{client: client, config: config}, nil
|
||||
}
|
||||
|
||||
// Timeout returns the timeout and interval to use when checking for DNS propagation.
|
||||
// Adjusting here to cope with spikes in propagation times.
|
||||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
authZone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cloudflare: %v", err)
|
||||
}
|
||||
|
||||
zoneID, err := d.client.ZoneIDByName(dns01.UnFqdn(authZone))
|
||||
if err != nil {
|
||||
return fmt.Errorf("cloudflare: failed to find zone %s: %v", authZone, err)
|
||||
}
|
||||
|
||||
dnsRecord := cloudflare.DNSRecord{
|
||||
Type: "TXT",
|
||||
Name: dns01.UnFqdn(fqdn),
|
||||
Content: value,
|
||||
TTL: d.config.TTL,
|
||||
}
|
||||
|
||||
response, err := d.client.CreateDNSRecord(zoneID, dnsRecord)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cloudflare: failed to create TXT record: %v", err)
|
||||
}
|
||||
|
||||
if !response.Success {
|
||||
return fmt.Errorf("cloudflare: failed to create TXT record: %+v %+v", response.Errors, response.Messages)
|
||||
}
|
||||
|
||||
log.Infof("cloudflare: new record for %s, ID %s", domain, response.Result.ID)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
authZone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cloudflare: %v", err)
|
||||
}
|
||||
|
||||
zoneID, err := d.client.ZoneIDByName(dns01.UnFqdn(authZone))
|
||||
if err != nil {
|
||||
return fmt.Errorf("cloudflare: failed to find zone %s: %v", authZone, err)
|
||||
}
|
||||
|
||||
dnsRecord := cloudflare.DNSRecord{
|
||||
Type: "TXT",
|
||||
Name: dns01.UnFqdn(fqdn),
|
||||
}
|
||||
|
||||
records, err := d.client.DNSRecords(zoneID, dnsRecord)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cloudflare: failed to find TXT records: %v", err)
|
||||
}
|
||||
|
||||
for _, record := range records {
|
||||
err = d.client.DeleteDNSRecord(zoneID, record.ID)
|
||||
if err != nil {
|
||||
log.Printf("cloudflare: failed to delete TXT record: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
108
vendor/github.com/go-acme/lego/providers/dns/cloudns/cloudns.go
generated
vendored
Normal file
108
vendor/github.com/go-acme/lego/providers/dns/cloudns/cloudns.go
generated
vendored
Normal file
|
@ -0,0 +1,108 @@
|
|||
// Package cloudns implements a DNS provider for solving the DNS-01 challenge using ClouDNS DNS.
|
||||
package cloudns
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
"github.com/go-acme/lego/providers/dns/cloudns/internal"
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
AuthID string
|
||||
AuthPassword string
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
PropagationTimeout: env.GetOrDefaultSecond("CLOUDNS_PROPAGATION_TIMEOUT", 120*time.Second),
|
||||
PollingInterval: env.GetOrDefaultSecond("CLOUDNS_POLLING_INTERVAL", 4*time.Second),
|
||||
TTL: env.GetOrDefaultInt("CLOUDNS_TTL", dns01.DefaultTTL),
|
||||
HTTPClient: &http.Client{
|
||||
Timeout: env.GetOrDefaultSecond("CLOUDNS_HTTP_TIMEOUT", 30*time.Second),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
client *internal.Client
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for ClouDNS.
|
||||
// Credentials must be passed in the environment variables:
|
||||
// CLOUDNS_AUTH_ID and CLOUDNS_AUTH_PASSWORD.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("CLOUDNS_AUTH_ID", "CLOUDNS_AUTH_PASSWORD")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ClouDNS: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.AuthID = values["CLOUDNS_AUTH_ID"]
|
||||
config.AuthPassword = values["CLOUDNS_AUTH_PASSWORD"]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for ClouDNS.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("ClouDNS: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
client, err := internal.NewClient(config.AuthID, config.AuthPassword)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client.HTTPClient = config.HTTPClient
|
||||
|
||||
return &DNSProvider{client: client, config: config}, nil
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge.
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
zone, err := d.client.GetZone(fqdn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return d.client.AddTxtRecord(zone.Name, fqdn, value, d.config.TTL)
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters.
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
zone, err := d.client.GetZone(fqdn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
record, err := d.client.FindTxtRecord(zone.Name, fqdn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return d.client.RemoveTxtRecord(record.ID, zone.Name)
|
||||
}
|
||||
|
||||
// Timeout returns the timeout and interval to use when checking for DNS propagation.
|
||||
// Adjusting here to cope with spikes in propagation times.
|
||||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
209
vendor/github.com/go-acme/lego/providers/dns/cloudns/internal/client.go
generated
vendored
Normal file
209
vendor/github.com/go-acme/lego/providers/dns/cloudns/internal/client.go
generated
vendored
Normal file
|
@ -0,0 +1,209 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
)
|
||||
|
||||
const defaultBaseURL = "https://api.cloudns.net/dns/"
|
||||
|
||||
type Zone struct {
|
||||
Name string
|
||||
Type string
|
||||
Zone string
|
||||
Status string // is an integer, but cast as string
|
||||
}
|
||||
|
||||
// TXTRecord a TXT record
|
||||
type TXTRecord struct {
|
||||
ID int `json:"id,string"`
|
||||
Type string `json:"type"`
|
||||
Host string `json:"host"`
|
||||
Record string `json:"record"`
|
||||
Failover int `json:"failover,string"`
|
||||
TTL int `json:"ttl,string"`
|
||||
Status int `json:"status"`
|
||||
}
|
||||
|
||||
type TXTRecords map[string]TXTRecord
|
||||
|
||||
// NewClient creates a ClouDNS client
|
||||
func NewClient(authID string, authPassword string) (*Client, error) {
|
||||
if authID == "" {
|
||||
return nil, fmt.Errorf("ClouDNS: credentials missing: authID")
|
||||
}
|
||||
|
||||
if authPassword == "" {
|
||||
return nil, fmt.Errorf("ClouDNS: credentials missing: authPassword")
|
||||
}
|
||||
|
||||
baseURL, err := url.Parse(defaultBaseURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Client{
|
||||
authID: authID,
|
||||
authPassword: authPassword,
|
||||
HTTPClient: &http.Client{},
|
||||
BaseURL: baseURL,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Client ClouDNS client
|
||||
type Client struct {
|
||||
authID string
|
||||
authPassword string
|
||||
HTTPClient *http.Client
|
||||
BaseURL *url.URL
|
||||
}
|
||||
|
||||
// GetZone Get domain name information for a FQDN
|
||||
func (c *Client) GetZone(authFQDN string) (*Zone, error) {
|
||||
authZone, err := dns01.FindZoneByFqdn(authFQDN)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
authZoneName := dns01.UnFqdn(authZone)
|
||||
|
||||
reqURL := *c.BaseURL
|
||||
reqURL.Path += "get-zone-info.json"
|
||||
|
||||
q := reqURL.Query()
|
||||
q.Add("domain-name", authZoneName)
|
||||
reqURL.RawQuery = q.Encode()
|
||||
|
||||
result, err := c.doRequest(http.MethodGet, &reqURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var zone Zone
|
||||
|
||||
if len(result) > 0 {
|
||||
if err = json.Unmarshal(result, &zone); err != nil {
|
||||
return nil, fmt.Errorf("ClouDNS: zone unmarshaling error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if zone.Name == authZoneName {
|
||||
return &zone, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("ClouDNS: zone %s not found for authFQDN %s", authZoneName, authFQDN)
|
||||
}
|
||||
|
||||
// FindTxtRecord return the TXT record a zone ID and a FQDN
|
||||
func (c *Client) FindTxtRecord(zoneName, fqdn string) (*TXTRecord, error) {
|
||||
host := dns01.UnFqdn(strings.TrimSuffix(dns01.UnFqdn(fqdn), zoneName))
|
||||
|
||||
reqURL := *c.BaseURL
|
||||
reqURL.Path += "records.json"
|
||||
|
||||
q := reqURL.Query()
|
||||
q.Add("domain-name", zoneName)
|
||||
q.Add("host", host)
|
||||
q.Add("type", "TXT")
|
||||
reqURL.RawQuery = q.Encode()
|
||||
|
||||
result, err := c.doRequest(http.MethodGet, &reqURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var records TXTRecords
|
||||
if err = json.Unmarshal(result, &records); err != nil {
|
||||
return nil, fmt.Errorf("ClouDNS: TXT record unmarshaling error: %v", err)
|
||||
}
|
||||
|
||||
for _, record := range records {
|
||||
if record.Host == host && record.Type == "TXT" {
|
||||
return &record, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("ClouDNS: no existing record found for %q", fqdn)
|
||||
}
|
||||
|
||||
// AddTxtRecord add a TXT record
|
||||
func (c *Client) AddTxtRecord(zoneName string, fqdn, value string, ttl int) error {
|
||||
host := dns01.UnFqdn(strings.TrimSuffix(dns01.UnFqdn(fqdn), zoneName))
|
||||
|
||||
reqURL := *c.BaseURL
|
||||
reqURL.Path += "add-record.json"
|
||||
|
||||
q := reqURL.Query()
|
||||
q.Add("domain-name", zoneName)
|
||||
q.Add("host", host)
|
||||
q.Add("record", value)
|
||||
q.Add("ttl", strconv.Itoa(ttl))
|
||||
q.Add("record-type", "TXT")
|
||||
reqURL.RawQuery = q.Encode()
|
||||
|
||||
_, err := c.doRequest(http.MethodPost, &reqURL)
|
||||
return err
|
||||
}
|
||||
|
||||
// RemoveTxtRecord remove a TXT record
|
||||
func (c *Client) RemoveTxtRecord(recordID int, zoneName string) error {
|
||||
reqURL := *c.BaseURL
|
||||
reqURL.Path += "delete-record.json"
|
||||
|
||||
q := reqURL.Query()
|
||||
q.Add("domain-name", zoneName)
|
||||
q.Add("record-id", strconv.Itoa(recordID))
|
||||
reqURL.RawQuery = q.Encode()
|
||||
|
||||
_, err := c.doRequest(http.MethodPost, &reqURL)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Client) doRequest(method string, url *url.URL) (json.RawMessage, error) {
|
||||
req, err := c.buildRequest(method, url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := c.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ClouDNS: %v", err)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
content, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ClouDNS: %s", toUnreadableBodyMessage(req, content))
|
||||
}
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return nil, fmt.Errorf("ClouDNS: invalid code (%v), error: %s", resp.StatusCode, content)
|
||||
}
|
||||
return content, nil
|
||||
}
|
||||
|
||||
func (c *Client) buildRequest(method string, url *url.URL) (*http.Request, error) {
|
||||
q := url.Query()
|
||||
q.Add("auth-id", c.authID)
|
||||
q.Add("auth-password", c.authPassword)
|
||||
url.RawQuery = q.Encode()
|
||||
|
||||
req, err := http.NewRequest(method, url.String(), nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ClouDNS: invalid request: %v", err)
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func toUnreadableBodyMessage(req *http.Request, rawBody []byte) string {
|
||||
return fmt.Sprintf("the request %s sent a response with a body which is an invalid format: %q", req.URL, string(rawBody))
|
||||
}
|
108
vendor/github.com/go-acme/lego/providers/dns/cloudxns/cloudxns.go
generated
vendored
Normal file
108
vendor/github.com/go-acme/lego/providers/dns/cloudxns/cloudxns.go
generated
vendored
Normal file
|
@ -0,0 +1,108 @@
|
|||
// Package cloudxns implements a DNS provider for solving the DNS-01 challenge using CloudXNS DNS.
|
||||
package cloudxns
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
"github.com/go-acme/lego/providers/dns/cloudxns/internal"
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
APIKey string
|
||||
SecretKey string
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
PropagationTimeout: env.GetOrDefaultSecond("CLOUDXNS_PROPAGATION_TIMEOUT", dns01.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("CLOUDXNS_POLLING_INTERVAL", dns01.DefaultPollingInterval),
|
||||
TTL: env.GetOrDefaultInt("CLOUDXNS_TTL", dns01.DefaultTTL),
|
||||
HTTPClient: &http.Client{
|
||||
Timeout: env.GetOrDefaultSecond("CLOUDXNS_HTTP_TIMEOUT", 30*time.Second),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
client *internal.Client
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for CloudXNS.
|
||||
// Credentials must be passed in the environment variables:
|
||||
// CLOUDXNS_API_KEY and CLOUDXNS_SECRET_KEY.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("CLOUDXNS_API_KEY", "CLOUDXNS_SECRET_KEY")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("CloudXNS: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.APIKey = values["CLOUDXNS_API_KEY"]
|
||||
config.SecretKey = values["CLOUDXNS_SECRET_KEY"]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for CloudXNS.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("CloudXNS: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
client, err := internal.NewClient(config.APIKey, config.SecretKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client.HTTPClient = config.HTTPClient
|
||||
|
||||
return &DNSProvider{client: client, config: config}, nil
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge.
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
info, err := d.client.GetDomainInformation(fqdn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return d.client.AddTxtRecord(info, fqdn, value, d.config.TTL)
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters.
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
info, err := d.client.GetDomainInformation(fqdn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
record, err := d.client.FindTxtRecord(info.ID, fqdn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return d.client.RemoveTxtRecord(record.RecordID, info.ID)
|
||||
}
|
||||
|
||||
// Timeout returns the timeout and interval to use when checking for DNS propagation.
|
||||
// Adjusting here to cope with spikes in propagation times.
|
||||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
208
vendor/github.com/go-acme/lego/providers/dns/cloudxns/internal/client.go
generated
vendored
Normal file
208
vendor/github.com/go-acme/lego/providers/dns/cloudxns/internal/client.go
generated
vendored
Normal file
|
@ -0,0 +1,208 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
)
|
||||
|
||||
const defaultBaseURL = "https://www.cloudxns.net/api2/"
|
||||
|
||||
type apiResponse struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Data json.RawMessage `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// Data Domain information
|
||||
type Data struct {
|
||||
ID string `json:"id"`
|
||||
Domain string `json:"domain"`
|
||||
TTL int `json:"ttl,omitempty"`
|
||||
}
|
||||
|
||||
// TXTRecord a TXT record
|
||||
type TXTRecord struct {
|
||||
ID int `json:"domain_id,omitempty"`
|
||||
RecordID string `json:"record_id,omitempty"`
|
||||
|
||||
Host string `json:"host"`
|
||||
Value string `json:"value"`
|
||||
Type string `json:"type"`
|
||||
LineID int `json:"line_id,string"`
|
||||
TTL int `json:"ttl,string"`
|
||||
}
|
||||
|
||||
// NewClient creates a CloudXNS client
|
||||
func NewClient(apiKey string, secretKey string) (*Client, error) {
|
||||
if apiKey == "" {
|
||||
return nil, fmt.Errorf("CloudXNS: credentials missing: apiKey")
|
||||
}
|
||||
|
||||
if secretKey == "" {
|
||||
return nil, fmt.Errorf("CloudXNS: credentials missing: secretKey")
|
||||
}
|
||||
|
||||
return &Client{
|
||||
apiKey: apiKey,
|
||||
secretKey: secretKey,
|
||||
HTTPClient: &http.Client{},
|
||||
BaseURL: defaultBaseURL,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Client CloudXNS client
|
||||
type Client struct {
|
||||
apiKey string
|
||||
secretKey string
|
||||
HTTPClient *http.Client
|
||||
BaseURL string
|
||||
}
|
||||
|
||||
// GetDomainInformation Get domain name information for a FQDN
|
||||
func (c *Client) GetDomainInformation(fqdn string) (*Data, error) {
|
||||
authZone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result, err := c.doRequest(http.MethodGet, "domain", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var domains []Data
|
||||
if len(result) > 0 {
|
||||
err = json.Unmarshal(result, &domains)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("CloudXNS: domains unmarshaling error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, data := range domains {
|
||||
if data.Domain == authZone {
|
||||
return &data, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("CloudXNS: zone %s not found for domain %s", authZone, fqdn)
|
||||
}
|
||||
|
||||
// FindTxtRecord return the TXT record a zone ID and a FQDN
|
||||
func (c *Client) FindTxtRecord(zoneID, fqdn string) (*TXTRecord, error) {
|
||||
result, err := c.doRequest(http.MethodGet, fmt.Sprintf("record/%s?host_id=0&offset=0&row_num=2000", zoneID), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var records []TXTRecord
|
||||
err = json.Unmarshal(result, &records)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("CloudXNS: TXT record unmarshaling error: %v", err)
|
||||
}
|
||||
|
||||
for _, record := range records {
|
||||
if record.Host == dns01.UnFqdn(fqdn) && record.Type == "TXT" {
|
||||
return &record, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("CloudXNS: no existing record found for %q", fqdn)
|
||||
}
|
||||
|
||||
// AddTxtRecord add a TXT record
|
||||
func (c *Client) AddTxtRecord(info *Data, fqdn, value string, ttl int) error {
|
||||
id, err := strconv.Atoi(info.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("CloudXNS: invalid zone ID: %v", err)
|
||||
}
|
||||
|
||||
payload := TXTRecord{
|
||||
ID: id,
|
||||
Host: dns01.UnFqdn(strings.TrimSuffix(fqdn, info.Domain)),
|
||||
Value: value,
|
||||
Type: "TXT",
|
||||
LineID: 1,
|
||||
TTL: ttl,
|
||||
}
|
||||
|
||||
body, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return fmt.Errorf("CloudXNS: record unmarshaling error: %v", err)
|
||||
}
|
||||
|
||||
_, err = c.doRequest(http.MethodPost, "record", body)
|
||||
return err
|
||||
}
|
||||
|
||||
// RemoveTxtRecord remove a TXT record
|
||||
func (c *Client) RemoveTxtRecord(recordID, zoneID string) error {
|
||||
_, err := c.doRequest(http.MethodDelete, fmt.Sprintf("record/%s/%s", recordID, zoneID), nil)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Client) doRequest(method, uri string, body []byte) (json.RawMessage, error) {
|
||||
req, err := c.buildRequest(method, uri, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := c.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("CloudXNS: %v", err)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
content, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("CloudXNS: %s", toUnreadableBodyMessage(req, content))
|
||||
}
|
||||
|
||||
var r apiResponse
|
||||
err = json.Unmarshal(content, &r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("CloudXNS: response unmashaling error: %v: %s", err, toUnreadableBodyMessage(req, content))
|
||||
}
|
||||
|
||||
if r.Code != 1 {
|
||||
return nil, fmt.Errorf("CloudXNS: invalid code (%v), error: %s", r.Code, r.Message)
|
||||
}
|
||||
return r.Data, nil
|
||||
}
|
||||
|
||||
func (c *Client) buildRequest(method, uri string, body []byte) (*http.Request, error) {
|
||||
url := c.BaseURL + uri
|
||||
|
||||
req, err := http.NewRequest(method, url, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("CloudXNS: invalid request: %v", err)
|
||||
}
|
||||
|
||||
requestDate := time.Now().Format(time.RFC1123Z)
|
||||
|
||||
req.Header.Set("API-KEY", c.apiKey)
|
||||
req.Header.Set("API-REQUEST-DATE", requestDate)
|
||||
req.Header.Set("API-HMAC", c.hmac(url, requestDate, string(body)))
|
||||
req.Header.Set("API-FORMAT", "json")
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func (c *Client) hmac(url, date, body string) string {
|
||||
sum := md5.Sum([]byte(c.apiKey + url + body + date + c.secretKey))
|
||||
return hex.EncodeToString(sum[:])
|
||||
}
|
||||
|
||||
func toUnreadableBodyMessage(req *http.Request, rawBody []byte) string {
|
||||
return fmt.Sprintf("the request %s sent a response with a body which is an invalid format: %q", req.URL, string(rawBody))
|
||||
}
|
148
vendor/github.com/go-acme/lego/providers/dns/conoha/conoha.go
generated
vendored
Normal file
148
vendor/github.com/go-acme/lego/providers/dns/conoha/conoha.go
generated
vendored
Normal file
|
@ -0,0 +1,148 @@
|
|||
// Package conoha implements a DNS provider for solving the DNS-01 challenge using ConoHa DNS.
|
||||
package conoha
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
"github.com/go-acme/lego/providers/dns/conoha/internal"
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
Region string
|
||||
TenantID string
|
||||
Username string
|
||||
Password string
|
||||
TTL int
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
Region: env.GetOrDefaultString("CONOHA_REGION", "tyo1"),
|
||||
TTL: env.GetOrDefaultInt("CONOHA_TTL", 60),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("CONOHA_PROPAGATION_TIMEOUT", dns01.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("CONOHA_POLLING_INTERVAL", dns01.DefaultPollingInterval),
|
||||
HTTPClient: &http.Client{
|
||||
Timeout: env.GetOrDefaultSecond("CONOHA_HTTP_TIMEOUT", 30*time.Second),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
client *internal.Client
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for ConoHa DNS.
|
||||
// Credentials must be passed in the environment variables: CONOHA_TENANT_ID, CONOHA_API_USERNAME, CONOHA_API_PASSWORD
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("CONOHA_TENANT_ID", "CONOHA_API_USERNAME", "CONOHA_API_PASSWORD")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("conoha: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.TenantID = values["CONOHA_TENANT_ID"]
|
||||
config.Username = values["CONOHA_API_USERNAME"]
|
||||
config.Password = values["CONOHA_API_PASSWORD"]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for ConoHa DNS.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("conoha: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if config.TenantID == "" || config.Username == "" || config.Password == "" {
|
||||
return nil, errors.New("conoha: some credentials information are missing")
|
||||
}
|
||||
|
||||
auth := internal.Auth{
|
||||
TenantID: config.TenantID,
|
||||
PasswordCredentials: internal.PasswordCredentials{
|
||||
Username: config.Username,
|
||||
Password: config.Password,
|
||||
},
|
||||
}
|
||||
|
||||
client, err := internal.NewClient(config.Region, auth, config.HTTPClient)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("conoha: failed to create client: %v", err)
|
||||
}
|
||||
|
||||
return &DNSProvider{config: config, client: client}, nil
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge.
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
authZone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
id, err := d.client.GetDomainID(authZone)
|
||||
if err != nil {
|
||||
return fmt.Errorf("conoha: failed to get domain ID: %v", err)
|
||||
}
|
||||
|
||||
record := internal.Record{
|
||||
Name: fqdn,
|
||||
Type: "TXT",
|
||||
Data: value,
|
||||
TTL: d.config.TTL,
|
||||
}
|
||||
|
||||
err = d.client.CreateRecord(id, record)
|
||||
if err != nil {
|
||||
return fmt.Errorf("conoha: failed to create record: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp clears ConoHa DNS TXT record
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
authZone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
domID, err := d.client.GetDomainID(authZone)
|
||||
if err != nil {
|
||||
return fmt.Errorf("conoha: failed to get domain ID: %v", err)
|
||||
}
|
||||
|
||||
recID, err := d.client.GetRecordID(domID, fqdn, "TXT", value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("conoha: failed to get record ID: %v", err)
|
||||
}
|
||||
|
||||
err = d.client.DeleteRecord(domID, recID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("conoha: failed to delete record: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Timeout returns the timeout and interval to use when checking for DNS propagation.
|
||||
// Adjusting here to cope with spikes in propagation times.
|
||||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
205
vendor/github.com/go-acme/lego/providers/dns/conoha/internal/client.go
generated
vendored
Normal file
205
vendor/github.com/go-acme/lego/providers/dns/conoha/internal/client.go
generated
vendored
Normal file
|
@ -0,0 +1,205 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const (
|
||||
identityBaseURL = "https://identity.%s.conoha.io"
|
||||
dnsServiceBaseURL = "https://dns-service.%s.conoha.io"
|
||||
)
|
||||
|
||||
// IdentityRequest is an authentication request body.
|
||||
type IdentityRequest struct {
|
||||
Auth Auth `json:"auth"`
|
||||
}
|
||||
|
||||
// Auth is an authentication information.
|
||||
type Auth struct {
|
||||
TenantID string `json:"tenantId"`
|
||||
PasswordCredentials PasswordCredentials `json:"passwordCredentials"`
|
||||
}
|
||||
|
||||
// PasswordCredentials is API-user's credentials.
|
||||
type PasswordCredentials struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
// IdentityResponse is an authentication response body.
|
||||
type IdentityResponse struct {
|
||||
Access Access `json:"access"`
|
||||
}
|
||||
|
||||
// Access is an identity information.
|
||||
type Access struct {
|
||||
Token Token `json:"token"`
|
||||
}
|
||||
|
||||
// Token is an api access token.
|
||||
type Token struct {
|
||||
ID string `json:"id"`
|
||||
}
|
||||
|
||||
// DomainListResponse is a response of a domain listing request.
|
||||
type DomainListResponse struct {
|
||||
Domains []Domain `json:"domains"`
|
||||
}
|
||||
|
||||
// Domain is a hosted domain entry.
|
||||
type Domain struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// RecordListResponse is a response of record listing request.
|
||||
type RecordListResponse struct {
|
||||
Records []Record `json:"records"`
|
||||
}
|
||||
|
||||
// Record is a record entry.
|
||||
type Record struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Data string `json:"data"`
|
||||
TTL int `json:"ttl"`
|
||||
}
|
||||
|
||||
// Client is a ConoHa API client.
|
||||
type Client struct {
|
||||
token string
|
||||
endpoint string
|
||||
httpClient *http.Client
|
||||
}
|
||||
|
||||
// NewClient returns a client instance logged into the ConoHa service.
|
||||
func NewClient(region string, auth Auth, httpClient *http.Client) (*Client, error) {
|
||||
if httpClient == nil {
|
||||
httpClient = &http.Client{}
|
||||
}
|
||||
|
||||
c := &Client{httpClient: httpClient}
|
||||
|
||||
c.endpoint = fmt.Sprintf(identityBaseURL, region)
|
||||
|
||||
identity, err := c.getIdentity(auth)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to login: %v", err)
|
||||
}
|
||||
|
||||
c.token = identity.Access.Token.ID
|
||||
c.endpoint = fmt.Sprintf(dnsServiceBaseURL, region)
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *Client) getIdentity(auth Auth) (*IdentityResponse, error) {
|
||||
req := &IdentityRequest{Auth: auth}
|
||||
|
||||
identity := &IdentityResponse{}
|
||||
|
||||
err := c.do(http.MethodPost, "/v2.0/tokens", req, identity)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return identity, nil
|
||||
}
|
||||
|
||||
// GetDomainID returns an ID of specified domain.
|
||||
func (c *Client) GetDomainID(domainName string) (string, error) {
|
||||
domainList := &DomainListResponse{}
|
||||
|
||||
err := c.do(http.MethodGet, "/v1/domains", nil, domainList)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, domain := range domainList.Domains {
|
||||
if domain.Name == domainName {
|
||||
return domain.ID, nil
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("no such domain: %s", domainName)
|
||||
}
|
||||
|
||||
// GetRecordID returns an ID of specified record.
|
||||
func (c *Client) GetRecordID(domainID, recordName, recordType, data string) (string, error) {
|
||||
recordList := &RecordListResponse{}
|
||||
|
||||
err := c.do(http.MethodGet, fmt.Sprintf("/v1/domains/%s/records", domainID), nil, recordList)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, record := range recordList.Records {
|
||||
if record.Name == recordName && record.Type == recordType && record.Data == data {
|
||||
return record.ID, nil
|
||||
}
|
||||
}
|
||||
return "", errors.New("no such record")
|
||||
}
|
||||
|
||||
// CreateRecord adds new record.
|
||||
func (c *Client) CreateRecord(domainID string, record Record) error {
|
||||
return c.do(http.MethodPost, fmt.Sprintf("/v1/domains/%s/records", domainID), record, nil)
|
||||
}
|
||||
|
||||
// DeleteRecord removes specified record.
|
||||
func (c *Client) DeleteRecord(domainID, recordID string) error {
|
||||
return c.do(http.MethodDelete, fmt.Sprintf("/v1/domains/%s/records/%s", domainID, recordID), nil, nil)
|
||||
}
|
||||
|
||||
func (c *Client) do(method, path string, payload, result interface{}) error {
|
||||
body := bytes.NewReader(nil)
|
||||
|
||||
if payload != nil {
|
||||
bodyBytes, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
body = bytes.NewReader(bodyBytes)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, c.endpoint+path, body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.Header.Set("Accept", "application/json")
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("X-Auth-Token", c.token)
|
||||
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
respBody, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
return fmt.Errorf("HTTP request failed with status code %d: %s", resp.StatusCode, string(respBody))
|
||||
}
|
||||
|
||||
if result != nil {
|
||||
respBody, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
return json.Unmarshal(respBody, result)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
249
vendor/github.com/go-acme/lego/providers/dns/designate/designate.go
generated
vendored
Normal file
249
vendor/github.com/go-acme/lego/providers/dns/designate/designate.go
generated
vendored
Normal file
|
@ -0,0 +1,249 @@
|
|||
// Package designate implements a DNS provider for solving the DNS-01 challenge using the Designate DNSaaS for Openstack.
|
||||
package designate
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/openstack"
|
||||
"github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets"
|
||||
"github.com/gophercloud/gophercloud/openstack/dns/v2/zones"
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
opts gophercloud.AuthOptions
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt("DESIGNATE_TTL", 10),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("DESIGNATE_PROPAGATION_TIMEOUT", 10*time.Minute),
|
||||
PollingInterval: env.GetOrDefaultSecond("DESIGNATE_POLLING_INTERVAL", 10*time.Second),
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider describes a provider for Designate
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
client *gophercloud.ServiceClient
|
||||
dnsEntriesMu sync.Mutex
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for Designate.
|
||||
// Credentials must be passed in the environment variables:
|
||||
// OS_AUTH_URL, OS_USERNAME, OS_PASSWORD, OS_TENANT_NAME, OS_REGION_NAME.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
_, err := env.Get("OS_AUTH_URL", "OS_USERNAME", "OS_PASSWORD", "OS_TENANT_NAME", "OS_REGION_NAME")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("designate: %v", err)
|
||||
}
|
||||
|
||||
opts, err := openstack.AuthOptionsFromEnv()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("designate: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.opts = opts
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for Designate.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("designate: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
provider, err := openstack.AuthenticatedClient(config.opts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("designate: failed to authenticate: %v", err)
|
||||
}
|
||||
|
||||
dnsClient, err := openstack.NewDNSV2(provider, gophercloud.EndpointOpts{
|
||||
Region: os.Getenv("OS_REGION_NAME"),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("designate: failed to get DNS provider: %v", err)
|
||||
}
|
||||
|
||||
return &DNSProvider{client: dnsClient, config: config}, nil
|
||||
}
|
||||
|
||||
// Timeout returns the timeout and interval to use when checking for DNS propagation.
|
||||
// Adjusting here to cope with spikes in propagation times.
|
||||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
authZone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("designate: couldn't get zone ID in Present: %v", err)
|
||||
}
|
||||
|
||||
zoneID, err := d.getZoneID(authZone)
|
||||
if err != nil {
|
||||
return fmt.Errorf("designate: %v", err)
|
||||
}
|
||||
|
||||
// use mutex to prevent race condition between creating the record and verifying it
|
||||
d.dnsEntriesMu.Lock()
|
||||
defer d.dnsEntriesMu.Unlock()
|
||||
|
||||
existingRecord, err := d.getRecord(zoneID, fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("designate: %v", err)
|
||||
}
|
||||
|
||||
if existingRecord != nil {
|
||||
if contains(existingRecord.Records, value) {
|
||||
log.Printf("designate: the record already exists: %s", value)
|
||||
return nil
|
||||
}
|
||||
|
||||
return d.updateRecord(existingRecord, value)
|
||||
}
|
||||
|
||||
err = d.createRecord(zoneID, fqdn, value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("designate: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
authZone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
zoneID, err := d.getZoneID(authZone)
|
||||
if err != nil {
|
||||
return fmt.Errorf("designate: couldn't get zone ID in CleanUp: %v", err)
|
||||
}
|
||||
|
||||
// use mutex to prevent race condition between getting the record and deleting it
|
||||
d.dnsEntriesMu.Lock()
|
||||
defer d.dnsEntriesMu.Unlock()
|
||||
|
||||
record, err := d.getRecord(zoneID, fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("designate: couldn't get Record ID in CleanUp: %v", err)
|
||||
}
|
||||
|
||||
if record == nil {
|
||||
// Record is already deleted
|
||||
return nil
|
||||
}
|
||||
|
||||
err = recordsets.Delete(d.client, zoneID, record.ID).ExtractErr()
|
||||
if err != nil {
|
||||
return fmt.Errorf("designate: error for %s in CleanUp: %v", fqdn, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func contains(values []string, value string) bool {
|
||||
for _, v := range values {
|
||||
if v == value {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (d *DNSProvider) createRecord(zoneID, fqdn, value string) error {
|
||||
createOpts := recordsets.CreateOpts{
|
||||
Name: fqdn,
|
||||
Type: "TXT",
|
||||
TTL: d.config.TTL,
|
||||
Description: "ACME verification record",
|
||||
Records: []string{value},
|
||||
}
|
||||
|
||||
actual, err := recordsets.Create(d.client, zoneID, createOpts).Extract()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error for %s in Present while creating record: %v", fqdn, err)
|
||||
}
|
||||
|
||||
if actual.Name != fqdn || actual.TTL != d.config.TTL {
|
||||
return fmt.Errorf("the created record doesn't match what we wanted to create")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) updateRecord(record *recordsets.RecordSet, value string) error {
|
||||
if contains(record.Records, value) {
|
||||
log.Printf("skip: the record already exists: %s", value)
|
||||
return nil
|
||||
}
|
||||
|
||||
values := append([]string{value}, record.Records...)
|
||||
|
||||
updateOpts := recordsets.UpdateOpts{
|
||||
Description: &record.Description,
|
||||
TTL: record.TTL,
|
||||
Records: values,
|
||||
}
|
||||
|
||||
result := recordsets.Update(d.client, record.ZoneID, record.ID, updateOpts)
|
||||
return result.Err
|
||||
}
|
||||
|
||||
func (d *DNSProvider) getZoneID(wanted string) (string, error) {
|
||||
allPages, err := zones.List(d.client, nil).AllPages()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
allZones, err := zones.ExtractZones(allPages)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, zone := range allZones {
|
||||
if zone.Name == wanted {
|
||||
return zone.ID, nil
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("zone id not found for %s", wanted)
|
||||
}
|
||||
|
||||
func (d *DNSProvider) getRecord(zoneID string, wanted string) (*recordsets.RecordSet, error) {
|
||||
allPages, err := recordsets.ListByZone(d.client, zoneID, nil).AllPages()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
allRecords, err := recordsets.ExtractRecordSets(allPages)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, record := range allRecords {
|
||||
if record.Name == wanted {
|
||||
return &record, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
132
vendor/github.com/go-acme/lego/providers/dns/digitalocean/client.go
generated
vendored
Normal file
132
vendor/github.com/go-acme/lego/providers/dns/digitalocean/client.go
generated
vendored
Normal file
|
@ -0,0 +1,132 @@
|
|||
package digitalocean
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
)
|
||||
|
||||
const defaultBaseURL = "https://api.digitalocean.com"
|
||||
|
||||
// txtRecordResponse represents a response from DO's API after making a TXT record
|
||||
type txtRecordResponse struct {
|
||||
DomainRecord record `json:"domain_record"`
|
||||
}
|
||||
|
||||
type record struct {
|
||||
ID int `json:"id,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Data string `json:"data,omitempty"`
|
||||
TTL int `json:"ttl,omitempty"`
|
||||
}
|
||||
|
||||
type apiError struct {
|
||||
ID string `json:"id"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
func (d *DNSProvider) removeTxtRecord(domain string, recordID int) error {
|
||||
authZone, err := dns01.FindZoneByFqdn(dns01.ToFqdn(domain))
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not determine zone for domain: '%s'. %s", domain, err)
|
||||
}
|
||||
|
||||
reqURL := fmt.Sprintf("%s/v2/domains/%s/records/%d", d.config.BaseURL, dns01.UnFqdn(authZone), recordID)
|
||||
req, err := d.newRequest(http.MethodDelete, reqURL, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := d.config.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode >= 400 {
|
||||
return readError(req, resp)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) addTxtRecord(domain, fqdn, value string) (*txtRecordResponse, error) {
|
||||
authZone, err := dns01.FindZoneByFqdn(dns01.ToFqdn(domain))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not determine zone for domain: '%s'. %s", domain, err)
|
||||
}
|
||||
|
||||
reqData := record{Type: "TXT", Name: fqdn, Data: value, TTL: d.config.TTL}
|
||||
body, err := json.Marshal(reqData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reqURL := fmt.Sprintf("%s/v2/domains/%s/records", d.config.BaseURL, dns01.UnFqdn(authZone))
|
||||
req, err := d.newRequest(http.MethodPost, reqURL, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := d.config.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode >= 400 {
|
||||
return nil, readError(req, resp)
|
||||
}
|
||||
|
||||
content, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, errors.New(toUnreadableBodyMessage(req, content))
|
||||
}
|
||||
|
||||
// Everything looks good; but we'll need the ID later to delete the record
|
||||
respData := &txtRecordResponse{}
|
||||
err = json.Unmarshal(content, respData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%v: %s", err, toUnreadableBodyMessage(req, content))
|
||||
}
|
||||
|
||||
return respData, nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) newRequest(method, reqURL string, body io.Reader) (*http.Request, error) {
|
||||
req, err := http.NewRequest(method, reqURL, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", d.config.AuthToken))
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func readError(req *http.Request, resp *http.Response) error {
|
||||
content, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return errors.New(toUnreadableBodyMessage(req, content))
|
||||
}
|
||||
|
||||
var errInfo apiError
|
||||
err = json.Unmarshal(content, &errInfo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("apiError unmarshaling error: %v: %s", err, toUnreadableBodyMessage(req, content))
|
||||
}
|
||||
|
||||
return fmt.Errorf("HTTP %d: %s: %s", resp.StatusCode, errInfo.ID, errInfo.Message)
|
||||
}
|
||||
|
||||
func toUnreadableBodyMessage(req *http.Request, rawBody []byte) string {
|
||||
return fmt.Sprintf("the request %s sent a response with a body which is an invalid format: %q", req.URL, string(rawBody))
|
||||
}
|
126
vendor/github.com/go-acme/lego/providers/dns/digitalocean/digitalocean.go
generated
vendored
Normal file
126
vendor/github.com/go-acme/lego/providers/dns/digitalocean/digitalocean.go
generated
vendored
Normal file
|
@ -0,0 +1,126 @@
|
|||
// Package digitalocean implements a DNS provider for solving the DNS-01 challenge using digitalocean DNS.
|
||||
package digitalocean
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
BaseURL string
|
||||
AuthToken string
|
||||
TTL int
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
BaseURL: defaultBaseURL,
|
||||
TTL: env.GetOrDefaultInt("DO_TTL", 30),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("DO_PROPAGATION_TIMEOUT", 60*time.Second),
|
||||
PollingInterval: env.GetOrDefaultSecond("DO_POLLING_INTERVAL", 5*time.Second),
|
||||
HTTPClient: &http.Client{
|
||||
Timeout: env.GetOrDefaultSecond("DO_HTTP_TIMEOUT", 30*time.Second),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface
|
||||
// that uses DigitalOcean's REST API to manage TXT records for a domain.
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
recordIDs map[string]int
|
||||
recordIDsMu sync.Mutex
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for Digital
|
||||
// Ocean. Credentials must be passed in the environment variable:
|
||||
// DO_AUTH_TOKEN.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("DO_AUTH_TOKEN")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("digitalocean: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.AuthToken = values["DO_AUTH_TOKEN"]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for Digital Ocean.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("digitalocean: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if config.AuthToken == "" {
|
||||
return nil, fmt.Errorf("digitalocean: credentials missing")
|
||||
}
|
||||
|
||||
if config.BaseURL == "" {
|
||||
config.BaseURL = defaultBaseURL
|
||||
}
|
||||
|
||||
return &DNSProvider{
|
||||
config: config,
|
||||
recordIDs: make(map[string]int),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Timeout returns the timeout and interval to use when checking for DNS propagation.
|
||||
// Adjusting here to cope with spikes in propagation times.
|
||||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
// Present creates a TXT record using the specified parameters
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
respData, err := d.addTxtRecord(domain, fqdn, value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("digitalocean: %v", err)
|
||||
}
|
||||
|
||||
d.recordIDsMu.Lock()
|
||||
d.recordIDs[fqdn] = respData.DomainRecord.ID
|
||||
d.recordIDsMu.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
// get the record's unique ID from when we created it
|
||||
d.recordIDsMu.Lock()
|
||||
recordID, ok := d.recordIDs[fqdn]
|
||||
d.recordIDsMu.Unlock()
|
||||
if !ok {
|
||||
return fmt.Errorf("digitalocean: unknown record ID for '%s'", fqdn)
|
||||
}
|
||||
|
||||
err := d.removeTxtRecord(domain, recordID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("digitalocean: %v", err)
|
||||
}
|
||||
|
||||
// Delete record ID from map
|
||||
d.recordIDsMu.Lock()
|
||||
delete(d.recordIDs, fqdn)
|
||||
d.recordIDsMu.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
177
vendor/github.com/go-acme/lego/providers/dns/dns_providers.go
generated
vendored
Normal file
177
vendor/github.com/go-acme/lego/providers/dns/dns_providers.go
generated
vendored
Normal file
|
@ -0,0 +1,177 @@
|
|||
package dns
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/go-acme/lego/challenge"
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/providers/dns/acmedns"
|
||||
"github.com/go-acme/lego/providers/dns/alidns"
|
||||
"github.com/go-acme/lego/providers/dns/auroradns"
|
||||
"github.com/go-acme/lego/providers/dns/azure"
|
||||
"github.com/go-acme/lego/providers/dns/bluecat"
|
||||
"github.com/go-acme/lego/providers/dns/cloudflare"
|
||||
"github.com/go-acme/lego/providers/dns/cloudns"
|
||||
"github.com/go-acme/lego/providers/dns/cloudxns"
|
||||
"github.com/go-acme/lego/providers/dns/conoha"
|
||||
"github.com/go-acme/lego/providers/dns/designate"
|
||||
"github.com/go-acme/lego/providers/dns/digitalocean"
|
||||
"github.com/go-acme/lego/providers/dns/dnsimple"
|
||||
"github.com/go-acme/lego/providers/dns/dnsmadeeasy"
|
||||
"github.com/go-acme/lego/providers/dns/dnspod"
|
||||
"github.com/go-acme/lego/providers/dns/dreamhost"
|
||||
"github.com/go-acme/lego/providers/dns/duckdns"
|
||||
"github.com/go-acme/lego/providers/dns/dyn"
|
||||
"github.com/go-acme/lego/providers/dns/exec"
|
||||
"github.com/go-acme/lego/providers/dns/exoscale"
|
||||
"github.com/go-acme/lego/providers/dns/fastdns"
|
||||
"github.com/go-acme/lego/providers/dns/gandi"
|
||||
"github.com/go-acme/lego/providers/dns/gandiv5"
|
||||
"github.com/go-acme/lego/providers/dns/gcloud"
|
||||
"github.com/go-acme/lego/providers/dns/glesys"
|
||||
"github.com/go-acme/lego/providers/dns/godaddy"
|
||||
"github.com/go-acme/lego/providers/dns/hostingde"
|
||||
"github.com/go-acme/lego/providers/dns/httpreq"
|
||||
"github.com/go-acme/lego/providers/dns/iij"
|
||||
"github.com/go-acme/lego/providers/dns/inwx"
|
||||
"github.com/go-acme/lego/providers/dns/lightsail"
|
||||
"github.com/go-acme/lego/providers/dns/linode"
|
||||
"github.com/go-acme/lego/providers/dns/linodev4"
|
||||
"github.com/go-acme/lego/providers/dns/mydnsjp"
|
||||
"github.com/go-acme/lego/providers/dns/namecheap"
|
||||
"github.com/go-acme/lego/providers/dns/namedotcom"
|
||||
"github.com/go-acme/lego/providers/dns/netcup"
|
||||
"github.com/go-acme/lego/providers/dns/nifcloud"
|
||||
"github.com/go-acme/lego/providers/dns/ns1"
|
||||
"github.com/go-acme/lego/providers/dns/oraclecloud"
|
||||
"github.com/go-acme/lego/providers/dns/otc"
|
||||
"github.com/go-acme/lego/providers/dns/ovh"
|
||||
"github.com/go-acme/lego/providers/dns/pdns"
|
||||
"github.com/go-acme/lego/providers/dns/rackspace"
|
||||
"github.com/go-acme/lego/providers/dns/rfc2136"
|
||||
"github.com/go-acme/lego/providers/dns/route53"
|
||||
"github.com/go-acme/lego/providers/dns/sakuracloud"
|
||||
"github.com/go-acme/lego/providers/dns/selectel"
|
||||
"github.com/go-acme/lego/providers/dns/stackpath"
|
||||
"github.com/go-acme/lego/providers/dns/transip"
|
||||
"github.com/go-acme/lego/providers/dns/vegadns"
|
||||
"github.com/go-acme/lego/providers/dns/vscale"
|
||||
"github.com/go-acme/lego/providers/dns/vultr"
|
||||
"github.com/go-acme/lego/providers/dns/zoneee"
|
||||
)
|
||||
|
||||
// NewDNSChallengeProviderByName Factory for DNS providers
|
||||
func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) {
|
||||
switch name {
|
||||
case "acme-dns":
|
||||
return acmedns.NewDNSProvider()
|
||||
case "alidns":
|
||||
return alidns.NewDNSProvider()
|
||||
case "azure":
|
||||
return azure.NewDNSProvider()
|
||||
case "auroradns":
|
||||
return auroradns.NewDNSProvider()
|
||||
case "bluecat":
|
||||
return bluecat.NewDNSProvider()
|
||||
case "cloudflare":
|
||||
return cloudflare.NewDNSProvider()
|
||||
case "cloudns":
|
||||
return cloudns.NewDNSProvider()
|
||||
case "cloudxns":
|
||||
return cloudxns.NewDNSProvider()
|
||||
case "conoha":
|
||||
return conoha.NewDNSProvider()
|
||||
case "designate":
|
||||
return designate.NewDNSProvider()
|
||||
case "digitalocean":
|
||||
return digitalocean.NewDNSProvider()
|
||||
case "dnsimple":
|
||||
return dnsimple.NewDNSProvider()
|
||||
case "dnsmadeeasy":
|
||||
return dnsmadeeasy.NewDNSProvider()
|
||||
case "dnspod":
|
||||
return dnspod.NewDNSProvider()
|
||||
case "dreamhost":
|
||||
return dreamhost.NewDNSProvider()
|
||||
case "duckdns":
|
||||
return duckdns.NewDNSProvider()
|
||||
case "dyn":
|
||||
return dyn.NewDNSProvider()
|
||||
case "fastdns":
|
||||
return fastdns.NewDNSProvider()
|
||||
case "exec":
|
||||
return exec.NewDNSProvider()
|
||||
case "exoscale":
|
||||
return exoscale.NewDNSProvider()
|
||||
case "gandi":
|
||||
return gandi.NewDNSProvider()
|
||||
case "gandiv5":
|
||||
return gandiv5.NewDNSProvider()
|
||||
case "glesys":
|
||||
return glesys.NewDNSProvider()
|
||||
case "gcloud":
|
||||
return gcloud.NewDNSProvider()
|
||||
case "godaddy":
|
||||
return godaddy.NewDNSProvider()
|
||||
case "hostingde":
|
||||
return hostingde.NewDNSProvider()
|
||||
case "httpreq":
|
||||
return httpreq.NewDNSProvider()
|
||||
case "iij":
|
||||
return iij.NewDNSProvider()
|
||||
case "inwx":
|
||||
return inwx.NewDNSProvider()
|
||||
case "lightsail":
|
||||
return lightsail.NewDNSProvider()
|
||||
case "linode":
|
||||
return linode.NewDNSProvider()
|
||||
case "linodev4":
|
||||
return linodev4.NewDNSProvider()
|
||||
case "manual":
|
||||
return dns01.NewDNSProviderManual()
|
||||
case "mydnsjp":
|
||||
return mydnsjp.NewDNSProvider()
|
||||
case "namecheap":
|
||||
return namecheap.NewDNSProvider()
|
||||
case "namedotcom":
|
||||
return namedotcom.NewDNSProvider()
|
||||
case "netcup":
|
||||
return netcup.NewDNSProvider()
|
||||
case "nifcloud":
|
||||
return nifcloud.NewDNSProvider()
|
||||
case "ns1":
|
||||
return ns1.NewDNSProvider()
|
||||
case "oraclecloud":
|
||||
return oraclecloud.NewDNSProvider()
|
||||
case "otc":
|
||||
return otc.NewDNSProvider()
|
||||
case "ovh":
|
||||
return ovh.NewDNSProvider()
|
||||
case "pdns":
|
||||
return pdns.NewDNSProvider()
|
||||
case "rackspace":
|
||||
return rackspace.NewDNSProvider()
|
||||
case "route53":
|
||||
return route53.NewDNSProvider()
|
||||
case "rfc2136":
|
||||
return rfc2136.NewDNSProvider()
|
||||
case "sakuracloud":
|
||||
return sakuracloud.NewDNSProvider()
|
||||
case "stackpath":
|
||||
return stackpath.NewDNSProvider()
|
||||
case "selectel":
|
||||
return selectel.NewDNSProvider()
|
||||
case "transip":
|
||||
return transip.NewDNSProvider()
|
||||
case "vegadns":
|
||||
return vegadns.NewDNSProvider()
|
||||
case "vultr":
|
||||
return vultr.NewDNSProvider()
|
||||
case "vscale":
|
||||
return vscale.NewDNSProvider()
|
||||
case "zoneee":
|
||||
return zoneee.NewDNSProvider()
|
||||
default:
|
||||
return nil, fmt.Errorf("unrecognised DNS provider: %s", name)
|
||||
}
|
||||
}
|
211
vendor/github.com/go-acme/lego/providers/dns/dnsimple/dnsimple.go
generated
vendored
Normal file
211
vendor/github.com/go-acme/lego/providers/dns/dnsimple/dnsimple.go
generated
vendored
Normal file
|
@ -0,0 +1,211 @@
|
|||
// Package dnsimple implements a DNS provider for solving the DNS-01 challenge using dnsimple DNS.
|
||||
package dnsimple
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/dnsimple/dnsimple-go/dnsimple"
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
AccessToken string
|
||||
BaseURL string
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt("DNSIMPLE_TTL", dns01.DefaultTTL),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("DNSIMPLE_PROPAGATION_TIMEOUT", dns01.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("DNSIMPLE_POLLING_INTERVAL", dns01.DefaultPollingInterval),
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface.
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
client *dnsimple.Client
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for dnsimple.
|
||||
// Credentials must be passed in the environment variables: DNSIMPLE_OAUTH_TOKEN.
|
||||
//
|
||||
// See: https://developer.dnsimple.com/v2/#authentication
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
config := NewDefaultConfig()
|
||||
config.AccessToken = env.GetOrFile("DNSIMPLE_OAUTH_TOKEN")
|
||||
config.BaseURL = env.GetOrFile("DNSIMPLE_BASE_URL")
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for DNSimple.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("dnsimple: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if config.AccessToken == "" {
|
||||
return nil, fmt.Errorf("dnsimple: OAuth token is missing")
|
||||
}
|
||||
|
||||
ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: config.AccessToken})
|
||||
client := dnsimple.NewClient(oauth2.NewClient(context.Background(), ts))
|
||||
|
||||
if config.BaseURL != "" {
|
||||
client.BaseURL = config.BaseURL
|
||||
}
|
||||
|
||||
return &DNSProvider{client: client, config: config}, nil
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge.
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
zoneName, err := d.getHostedZone(domain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dnsimple: %v", err)
|
||||
}
|
||||
|
||||
accountID, err := d.getAccountID()
|
||||
if err != nil {
|
||||
return fmt.Errorf("dnsimple: %v", err)
|
||||
}
|
||||
|
||||
recordAttributes := newTxtRecord(zoneName, fqdn, value, d.config.TTL)
|
||||
_, err = d.client.Zones.CreateRecord(accountID, zoneName, recordAttributes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dnsimple: API call failed: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters.
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
records, err := d.findTxtRecords(domain, fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dnsimple: %v", err)
|
||||
}
|
||||
|
||||
accountID, err := d.getAccountID()
|
||||
if err != nil {
|
||||
return fmt.Errorf("dnsimple: %v", err)
|
||||
}
|
||||
|
||||
var lastErr error
|
||||
for _, rec := range records {
|
||||
_, err := d.client.Zones.DeleteRecord(accountID, rec.ZoneID, rec.ID)
|
||||
if err != nil {
|
||||
lastErr = fmt.Errorf("dnsimple: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return lastErr
|
||||
}
|
||||
|
||||
// Timeout returns the timeout and interval to use when checking for DNS propagation.
|
||||
// Adjusting here to cope with spikes in propagation times.
|
||||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
func (d *DNSProvider) getHostedZone(domain string) (string, error) {
|
||||
authZone, err := dns01.FindZoneByFqdn(dns01.ToFqdn(domain))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
accountID, err := d.getAccountID()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
zoneName := dns01.UnFqdn(authZone)
|
||||
|
||||
zones, err := d.client.Zones.ListZones(accountID, &dnsimple.ZoneListOptions{NameLike: zoneName})
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("API call failed: %v", err)
|
||||
}
|
||||
|
||||
var hostedZone dnsimple.Zone
|
||||
for _, zone := range zones.Data {
|
||||
if zone.Name == zoneName {
|
||||
hostedZone = zone
|
||||
}
|
||||
}
|
||||
|
||||
if hostedZone.ID == 0 {
|
||||
return "", fmt.Errorf("zone %s not found in DNSimple for domain %s", authZone, domain)
|
||||
}
|
||||
|
||||
return hostedZone.Name, nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) findTxtRecords(domain, fqdn string) ([]dnsimple.ZoneRecord, error) {
|
||||
zoneName, err := d.getHostedZone(domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
accountID, err := d.getAccountID()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
recordName := extractRecordName(fqdn, zoneName)
|
||||
|
||||
result, err := d.client.Zones.ListRecords(accountID, zoneName, &dnsimple.ZoneRecordListOptions{Name: recordName, Type: "TXT", ListOptions: dnsimple.ListOptions{}})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("API call has failed: %v", err)
|
||||
}
|
||||
|
||||
return result.Data, nil
|
||||
}
|
||||
|
||||
func newTxtRecord(zoneName, fqdn, value string, ttl int) dnsimple.ZoneRecord {
|
||||
name := extractRecordName(fqdn, zoneName)
|
||||
|
||||
return dnsimple.ZoneRecord{
|
||||
Type: "TXT",
|
||||
Name: name,
|
||||
Content: value,
|
||||
TTL: ttl,
|
||||
}
|
||||
}
|
||||
|
||||
func extractRecordName(fqdn, domain string) string {
|
||||
name := dns01.UnFqdn(fqdn)
|
||||
if idx := strings.Index(name, "."+domain); idx != -1 {
|
||||
return name[:idx]
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
func (d *DNSProvider) getAccountID() (string, error) {
|
||||
whoamiResponse, err := d.client.Identity.Whoami()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if whoamiResponse.Data.Account == nil {
|
||||
return "", fmt.Errorf("user tokens are not supported, please use an account token")
|
||||
}
|
||||
|
||||
return strconv.FormatInt(whoamiResponse.Data.Account.ID, 10), nil
|
||||
}
|
163
vendor/github.com/go-acme/lego/providers/dns/dnsmadeeasy/dnsmadeeasy.go
generated
vendored
Normal file
163
vendor/github.com/go-acme/lego/providers/dns/dnsmadeeasy/dnsmadeeasy.go
generated
vendored
Normal file
|
@ -0,0 +1,163 @@
|
|||
// Package dnsmadeeasy implements a DNS provider for solving the DNS-01 challenge using DNS Made Easy.
|
||||
package dnsmadeeasy
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
"github.com/go-acme/lego/providers/dns/dnsmadeeasy/internal"
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
BaseURL string
|
||||
APIKey string
|
||||
APISecret string
|
||||
Sandbox bool
|
||||
HTTPClient *http.Client
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt("DNSMADEEASY_TTL", dns01.DefaultTTL),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("DNSMADEEASY_PROPAGATION_TIMEOUT", dns01.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("DNSMADEEASY_POLLING_INTERVAL", dns01.DefaultPollingInterval),
|
||||
HTTPClient: &http.Client{
|
||||
Timeout: env.GetOrDefaultSecond("DNSMADEEASY_HTTP_TIMEOUT", 10*time.Second),
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface that uses
|
||||
// DNSMadeEasy's DNS API to manage TXT records for a domain.
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
client *internal.Client
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for DNSMadeEasy DNS.
|
||||
// Credentials must be passed in the environment variables:
|
||||
// DNSMADEEASY_API_KEY and DNSMADEEASY_API_SECRET.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("DNSMADEEASY_API_KEY", "DNSMADEEASY_API_SECRET")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("dnsmadeeasy: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.Sandbox = env.GetOrDefaultBool("DNSMADEEASY_SANDBOX", false)
|
||||
config.APIKey = values["DNSMADEEASY_API_KEY"]
|
||||
config.APISecret = values["DNSMADEEASY_API_SECRET"]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for DNS Made Easy.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("dnsmadeeasy: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
var baseURL string
|
||||
if config.Sandbox {
|
||||
baseURL = "https://api.sandbox.dnsmadeeasy.com/V2.0"
|
||||
} else {
|
||||
if len(config.BaseURL) > 0 {
|
||||
baseURL = config.BaseURL
|
||||
} else {
|
||||
baseURL = "https://api.dnsmadeeasy.com/V2.0"
|
||||
}
|
||||
}
|
||||
|
||||
client, err := internal.NewClient(config.APIKey, config.APISecret)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("dnsmadeeasy: %v", err)
|
||||
}
|
||||
|
||||
client.HTTPClient = config.HTTPClient
|
||||
client.BaseURL = baseURL
|
||||
|
||||
return &DNSProvider{
|
||||
client: client,
|
||||
config: config,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Present creates a TXT record using the specified parameters
|
||||
func (d *DNSProvider) Present(domainName, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domainName, keyAuth)
|
||||
|
||||
authZone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dnsmadeeasy: unable to find zone for %s: %v", fqdn, err)
|
||||
}
|
||||
|
||||
// fetch the domain details
|
||||
domain, err := d.client.GetDomain(authZone)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dnsmadeeasy: unable to get domain for zone %s: %v", authZone, err)
|
||||
}
|
||||
|
||||
// create the TXT record
|
||||
name := strings.Replace(fqdn, "."+authZone, "", 1)
|
||||
record := &internal.Record{Type: "TXT", Name: name, Value: value, TTL: d.config.TTL}
|
||||
|
||||
err = d.client.CreateRecord(domain, record)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dnsmadeeasy: unable to create record for %s: %v", name, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT records matching the specified parameters
|
||||
func (d *DNSProvider) CleanUp(domainName, token, keyAuth string) error {
|
||||
fqdn, _ := dns01.GetRecord(domainName, keyAuth)
|
||||
|
||||
authZone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dnsmadeeasy: unable to find zone for %s: %v", fqdn, err)
|
||||
}
|
||||
|
||||
// fetch the domain details
|
||||
domain, err := d.client.GetDomain(authZone)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dnsmadeeasy: unable to get domain for zone %s: %v", authZone, err)
|
||||
}
|
||||
|
||||
// find matching records
|
||||
name := strings.Replace(fqdn, "."+authZone, "", 1)
|
||||
records, err := d.client.GetRecords(domain, name, "TXT")
|
||||
if err != nil {
|
||||
return fmt.Errorf("dnsmadeeasy: unable to get records for domain %s: %v", domain.Name, err)
|
||||
}
|
||||
|
||||
// delete records
|
||||
var lastError error
|
||||
for _, record := range *records {
|
||||
err = d.client.DeleteRecord(record)
|
||||
if err != nil {
|
||||
lastError = fmt.Errorf("dnsmadeeasy: unable to delete record [id=%d, name=%s]: %v", record.ID, record.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
return lastError
|
||||
}
|
||||
|
||||
// Timeout returns the timeout and interval to use when checking for DNS propagation.
|
||||
// Adjusting here to cope with spikes in propagation times.
|
||||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
173
vendor/github.com/go-acme/lego/providers/dns/dnsmadeeasy/internal/client.go
generated
vendored
Normal file
173
vendor/github.com/go-acme/lego/providers/dns/dnsmadeeasy/internal/client.go
generated
vendored
Normal file
|
@ -0,0 +1,173 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Domain holds the DNSMadeEasy API representation of a Domain
|
||||
type Domain struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// Record holds the DNSMadeEasy API representation of a Domain Record
|
||||
type Record struct {
|
||||
ID int `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
TTL int `json:"ttl"`
|
||||
SourceID int `json:"sourceId"`
|
||||
}
|
||||
|
||||
type recordsResponse struct {
|
||||
Records *[]Record `json:"data"`
|
||||
}
|
||||
|
||||
// Client DNSMadeEasy client
|
||||
type Client struct {
|
||||
apiKey string
|
||||
apiSecret string
|
||||
BaseURL string
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// NewClient creates a DNSMadeEasy client
|
||||
func NewClient(apiKey string, apiSecret string) (*Client, error) {
|
||||
if apiKey == "" {
|
||||
return nil, fmt.Errorf("credentials missing: API key")
|
||||
}
|
||||
|
||||
if apiSecret == "" {
|
||||
return nil, fmt.Errorf("credentials missing: API secret")
|
||||
}
|
||||
|
||||
return &Client{
|
||||
apiKey: apiKey,
|
||||
apiSecret: apiSecret,
|
||||
HTTPClient: &http.Client{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetDomain gets a domain
|
||||
func (c *Client) GetDomain(authZone string) (*Domain, error) {
|
||||
domainName := authZone[0 : len(authZone)-1]
|
||||
resource := fmt.Sprintf("%s%s", "/dns/managed/name?domainname=", domainName)
|
||||
|
||||
resp, err := c.sendRequest(http.MethodGet, resource, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
domain := &Domain{}
|
||||
err = json.NewDecoder(resp.Body).Decode(&domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return domain, nil
|
||||
}
|
||||
|
||||
// GetRecords gets all TXT records
|
||||
func (c *Client) GetRecords(domain *Domain, recordName, recordType string) (*[]Record, error) {
|
||||
resource := fmt.Sprintf("%s/%d/%s%s%s%s", "/dns/managed", domain.ID, "records?recordName=", recordName, "&type=", recordType)
|
||||
|
||||
resp, err := c.sendRequest(http.MethodGet, resource, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
records := &recordsResponse{}
|
||||
err = json.NewDecoder(resp.Body).Decode(&records)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return records.Records, nil
|
||||
}
|
||||
|
||||
// CreateRecord creates a TXT records
|
||||
func (c *Client) CreateRecord(domain *Domain, record *Record) error {
|
||||
url := fmt.Sprintf("%s/%d/%s", "/dns/managed", domain.ID, "records")
|
||||
|
||||
resp, err := c.sendRequest(http.MethodPost, url, record)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteRecord deletes a TXT records
|
||||
func (c *Client) DeleteRecord(record Record) error {
|
||||
resource := fmt.Sprintf("%s/%d/%s/%d", "/dns/managed", record.SourceID, "records", record.ID)
|
||||
|
||||
resp, err := c.sendRequest(http.MethodDelete, resource, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) sendRequest(method, resource string, payload interface{}) (*http.Response, error) {
|
||||
url := fmt.Sprintf("%s%s", c.BaseURL, resource)
|
||||
|
||||
body, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
timestamp := time.Now().UTC().Format(time.RFC1123)
|
||||
signature, err := computeHMAC(timestamp, c.apiSecret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, url, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("x-dnsme-apiKey", c.apiKey)
|
||||
req.Header.Set("x-dnsme-requestDate", timestamp)
|
||||
req.Header.Set("x-dnsme-hmac", signature)
|
||||
req.Header.Set("accept", "application/json")
|
||||
req.Header.Set("content-type", "application/json")
|
||||
|
||||
resp, err := c.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode > 299 {
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("request failed with HTTP status code %d", resp.StatusCode)
|
||||
}
|
||||
return nil, fmt.Errorf("request failed with HTTP status code %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func computeHMAC(message string, secret string) (string, error) {
|
||||
key := []byte(secret)
|
||||
h := hmac.New(sha1.New, key)
|
||||
_, err := h.Write([]byte(message))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return hex.EncodeToString(h.Sum(nil)), nil
|
||||
}
|
189
vendor/github.com/go-acme/lego/providers/dns/dnspod/dnspod.go
generated
vendored
Normal file
189
vendor/github.com/go-acme/lego/providers/dns/dnspod/dnspod.go
generated
vendored
Normal file
|
@ -0,0 +1,189 @@
|
|||
// Package dnspod implements a DNS provider for solving the DNS-01 challenge using dnspod DNS.
|
||||
package dnspod
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
dnspod "github.com/decker502/dnspod-go"
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
LoginToken string
|
||||
TTL int
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt("DNSPOD_TTL", 600),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("DNSPOD_PROPAGATION_TIMEOUT", dns01.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("DNSPOD_POLLING_INTERVAL", dns01.DefaultPollingInterval),
|
||||
HTTPClient: &http.Client{
|
||||
Timeout: env.GetOrDefaultSecond("DNSPOD_HTTP_TIMEOUT", 0),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface.
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
client *dnspod.Client
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for dnspod.
|
||||
// Credentials must be passed in the environment variables: DNSPOD_API_KEY.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("DNSPOD_API_KEY")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("dnspod: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.LoginToken = values["DNSPOD_API_KEY"]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for dnspod.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("dnspod: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if config.LoginToken == "" {
|
||||
return nil, fmt.Errorf("dnspod: credentials missing")
|
||||
}
|
||||
|
||||
params := dnspod.CommonParams{LoginToken: config.LoginToken, Format: "json"}
|
||||
|
||||
client := dnspod.NewClient(params)
|
||||
client.HttpClient = config.HTTPClient
|
||||
|
||||
return &DNSProvider{client: client, config: config}, nil
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge.
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
zoneID, zoneName, err := d.getHostedZone(domain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
recordAttributes := d.newTxtRecord(zoneName, fqdn, value, d.config.TTL)
|
||||
_, _, err = d.client.Domains.CreateRecord(zoneID, *recordAttributes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("API call failed: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters.
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
records, err := d.findTxtRecords(domain, fqdn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
zoneID, _, err := d.getHostedZone(domain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, rec := range records {
|
||||
_, err := d.client.Domains.DeleteRecord(zoneID, rec.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Timeout returns the timeout and interval to use when checking for DNS propagation.
|
||||
// Adjusting here to cope with spikes in propagation times.
|
||||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
func (d *DNSProvider) getHostedZone(domain string) (string, string, error) {
|
||||
zones, _, err := d.client.Domains.List()
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("API call failed: %v", err)
|
||||
}
|
||||
|
||||
authZone, err := dns01.FindZoneByFqdn(dns01.ToFqdn(domain))
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
var hostedZone dnspod.Domain
|
||||
for _, zone := range zones {
|
||||
if zone.Name == dns01.UnFqdn(authZone) {
|
||||
hostedZone = zone
|
||||
}
|
||||
}
|
||||
|
||||
if hostedZone.ID == 0 {
|
||||
return "", "", fmt.Errorf("zone %s not found in dnspod for domain %s", authZone, domain)
|
||||
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%v", hostedZone.ID), hostedZone.Name, nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) newTxtRecord(zone, fqdn, value string, ttl int) *dnspod.Record {
|
||||
name := d.extractRecordName(fqdn, zone)
|
||||
|
||||
return &dnspod.Record{
|
||||
Type: "TXT",
|
||||
Name: name,
|
||||
Value: value,
|
||||
Line: "默认",
|
||||
TTL: strconv.Itoa(ttl),
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DNSProvider) findTxtRecords(domain, fqdn string) ([]dnspod.Record, error) {
|
||||
zoneID, zoneName, err := d.getHostedZone(domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var records []dnspod.Record
|
||||
result, _, err := d.client.Domains.ListRecords(zoneID, "")
|
||||
if err != nil {
|
||||
return records, fmt.Errorf("API call has failed: %v", err)
|
||||
}
|
||||
|
||||
recordName := d.extractRecordName(fqdn, zoneName)
|
||||
|
||||
for _, record := range result {
|
||||
if record.Name == recordName {
|
||||
records = append(records, record)
|
||||
}
|
||||
}
|
||||
|
||||
return records, nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) extractRecordName(fqdn, domain string) string {
|
||||
name := dns01.UnFqdn(fqdn)
|
||||
if idx := strings.Index(name, "."+domain); idx != -1 {
|
||||
return name[:idx]
|
||||
}
|
||||
return name
|
||||
}
|
73
vendor/github.com/go-acme/lego/providers/dns/dreamhost/client.go
generated
vendored
Normal file
73
vendor/github.com/go-acme/lego/providers/dns/dreamhost/client.go
generated
vendored
Normal file
|
@ -0,0 +1,73 @@
|
|||
package dreamhost
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
|
||||
"github.com/go-acme/lego/log"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultBaseURL = "https://api.dreamhost.com"
|
||||
|
||||
cmdAddRecord = "dns-add_record"
|
||||
cmdRemoveRecord = "dns-remove_record"
|
||||
)
|
||||
|
||||
type apiResponse struct {
|
||||
Data string `json:"data"`
|
||||
Result string `json:"result"`
|
||||
}
|
||||
|
||||
func (d *DNSProvider) buildQuery(action, domain, txt string) (*url.URL, error) {
|
||||
u, err := url.Parse(d.config.BaseURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
query := u.Query()
|
||||
query.Set("key", d.config.APIKey)
|
||||
query.Set("cmd", action)
|
||||
query.Set("format", "json")
|
||||
query.Set("record", domain)
|
||||
query.Set("type", "TXT")
|
||||
query.Set("value", txt)
|
||||
query.Set("comment", url.QueryEscape("Managed By lego"))
|
||||
u.RawQuery = query.Encode()
|
||||
|
||||
return u, nil
|
||||
}
|
||||
|
||||
// updateTxtRecord will either add or remove a TXT record.
|
||||
// action is either cmdAddRecord or cmdRemoveRecord
|
||||
func (d *DNSProvider) updateTxtRecord(u fmt.Stringer) error {
|
||||
resp, err := d.config.HTTPClient.Get(u.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return fmt.Errorf("request failed with HTTP status code %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
raw, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read body: %v", err)
|
||||
}
|
||||
|
||||
var response apiResponse
|
||||
err = json.Unmarshal(raw, &response)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to decode API server response: %v: %s", err, string(raw))
|
||||
}
|
||||
|
||||
if response.Result == "error" {
|
||||
return fmt.Errorf("add TXT record failed: %s", response.Data)
|
||||
}
|
||||
|
||||
log.Infof("dreamhost: %s", response.Data)
|
||||
return nil
|
||||
}
|
111
vendor/github.com/go-acme/lego/providers/dns/dreamhost/dreamhost.go
generated
vendored
Normal file
111
vendor/github.com/go-acme/lego/providers/dns/dreamhost/dreamhost.go
generated
vendored
Normal file
|
@ -0,0 +1,111 @@
|
|||
// Package dreamhost implements a DNS provider for solving the DNS-01 challenge using DreamHost.
|
||||
// See https://help.dreamhost.com/hc/en-us/articles/217560167-API_overview
|
||||
// and https://help.dreamhost.com/hc/en-us/articles/217555707-DNS-API-commands for the API spec.
|
||||
package dreamhost
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
BaseURL string
|
||||
APIKey string
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
BaseURL: defaultBaseURL,
|
||||
PropagationTimeout: env.GetOrDefaultSecond("DREAMHOST_PROPAGATION_TIMEOUT", 60*time.Minute),
|
||||
PollingInterval: env.GetOrDefaultSecond("DREAMHOST_POLLING_INTERVAL", 1*time.Minute),
|
||||
HTTPClient: &http.Client{
|
||||
Timeout: env.GetOrDefaultSecond("DREAMHOST_HTTP_TIMEOUT", 30*time.Second),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider adds and removes the record for the DNS challenge
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a new DNS provider using
|
||||
// environment variable DREAMHOST_TOKEN for adding and removing the DNS record.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("DREAMHOST_API_KEY")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("dreamhost: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.APIKey = values["DREAMHOST_API_KEY"]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for DreamHost.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("dreamhost: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if config.APIKey == "" {
|
||||
return nil, errors.New("dreamhost: credentials missing")
|
||||
}
|
||||
|
||||
if config.BaseURL == "" {
|
||||
config.BaseURL = defaultBaseURL
|
||||
}
|
||||
|
||||
return &DNSProvider{config: config}, nil
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge.
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
record := dns01.UnFqdn(fqdn)
|
||||
|
||||
u, err := d.buildQuery(cmdAddRecord, record, value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dreamhost: %v", err)
|
||||
}
|
||||
|
||||
err = d.updateTxtRecord(u)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dreamhost: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp clears DreamHost TXT record
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
record := dns01.UnFqdn(fqdn)
|
||||
|
||||
u, err := d.buildQuery(cmdRemoveRecord, record, value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dreamhost: %v", err)
|
||||
}
|
||||
|
||||
err = d.updateTxtRecord(u)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dreamhost: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Timeout returns the timeout and interval to use when checking for DNS propagation.
|
||||
// Adjusting here to cope with spikes in propagation times.
|
||||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
68
vendor/github.com/go-acme/lego/providers/dns/duckdns/client.go
generated
vendored
Normal file
68
vendor/github.com/go-acme/lego/providers/dns/duckdns/client.go
generated
vendored
Normal file
|
@ -0,0 +1,68 @@
|
|||
package duckdns
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// updateTxtRecord Update the domains TXT record
|
||||
// To update the TXT record we just need to make one simple get request.
|
||||
// In DuckDNS you only have one TXT record shared with the domain and all sub domains.
|
||||
func (d *DNSProvider) updateTxtRecord(domain, token, txt string, clear bool) error {
|
||||
u, _ := url.Parse("https://www.duckdns.org/update")
|
||||
|
||||
mainDomain := getMainDomain(domain)
|
||||
if len(mainDomain) == 0 {
|
||||
return fmt.Errorf("unable to find the main domain for: %s", domain)
|
||||
}
|
||||
|
||||
query := u.Query()
|
||||
query.Set("domains", mainDomain)
|
||||
query.Set("token", token)
|
||||
query.Set("clear", strconv.FormatBool(clear))
|
||||
query.Set("txt", txt)
|
||||
u.RawQuery = query.Encode()
|
||||
|
||||
response, err := d.config.HTTPClient.Get(u.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
bodyBytes, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
body := string(bodyBytes)
|
||||
if body != "OK" {
|
||||
return fmt.Errorf("request to change TXT record for DuckDNS returned the following result (%s) this does not match expectation (OK) used url [%s]", body, u)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DuckDNS only lets you write to your subdomain
|
||||
// so it must be in format subdomain.duckdns.org
|
||||
// not in format subsubdomain.subdomain.duckdns.org
|
||||
// so strip off everything that is not top 3 levels
|
||||
func getMainDomain(domain string) string {
|
||||
domain = dns01.UnFqdn(domain)
|
||||
|
||||
split := dns.Split(domain)
|
||||
if strings.HasSuffix(strings.ToLower(domain), "duckdns.org") {
|
||||
if len(split) < 3 {
|
||||
return ""
|
||||
}
|
||||
|
||||
firstSubDomainIndex := split[len(split)-3]
|
||||
return domain[firstSubDomainIndex:]
|
||||
}
|
||||
|
||||
return domain[split[len(split)-1]:]
|
||||
}
|
89
vendor/github.com/go-acme/lego/providers/dns/duckdns/duckdns.go
generated
vendored
Normal file
89
vendor/github.com/go-acme/lego/providers/dns/duckdns/duckdns.go
generated
vendored
Normal file
|
@ -0,0 +1,89 @@
|
|||
// Package duckdns implements a DNS provider for solving the DNS-01 challenge using DuckDNS.
|
||||
// See http://www.duckdns.org/spec.jsp for more info on updating TXT records.
|
||||
package duckdns
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
Token string
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
SequenceInterval time.Duration
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
PropagationTimeout: env.GetOrDefaultSecond("DUCKDNS_PROPAGATION_TIMEOUT", dns01.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("DUCKDNS_POLLING_INTERVAL", dns01.DefaultPollingInterval),
|
||||
SequenceInterval: env.GetOrDefaultSecond("DUCKDNS_SEQUENCE_INTERVAL", dns01.DefaultPropagationTimeout),
|
||||
HTTPClient: &http.Client{
|
||||
Timeout: env.GetOrDefaultSecond("DUCKDNS_HTTP_TIMEOUT", 30*time.Second),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider adds and removes the record for the DNS challenge
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a new DNS provider using
|
||||
// environment variable DUCKDNS_TOKEN for adding and removing the DNS record.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("DUCKDNS_TOKEN")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("duckdns: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.Token = values["DUCKDNS_TOKEN"]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for DuckDNS.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("duckdns: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if config.Token == "" {
|
||||
return nil, errors.New("duckdns: credentials missing")
|
||||
}
|
||||
|
||||
return &DNSProvider{config: config}, nil
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge.
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
_, txtRecord := dns01.GetRecord(domain, keyAuth)
|
||||
return d.updateTxtRecord(domain, d.config.Token, txtRecord, false)
|
||||
}
|
||||
|
||||
// CleanUp clears DuckDNS TXT record
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
return d.updateTxtRecord(domain, d.config.Token, "", true)
|
||||
}
|
||||
|
||||
// Timeout returns the timeout and interval to use when checking for DNS propagation.
|
||||
// Adjusting here to cope with spikes in propagation times.
|
||||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
// Sequential All DNS challenges for this provider will be resolved sequentially.
|
||||
// Returns the interval between each iteration.
|
||||
func (d *DNSProvider) Sequential() time.Duration {
|
||||
return d.config.SequenceInterval
|
||||
}
|
146
vendor/github.com/go-acme/lego/providers/dns/dyn/client.go
generated
vendored
Normal file
146
vendor/github.com/go-acme/lego/providers/dns/dyn/client.go
generated
vendored
Normal file
|
@ -0,0 +1,146 @@
|
|||
package dyn
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const defaultBaseURL = "https://api.dynect.net/REST"
|
||||
|
||||
type dynResponse struct {
|
||||
// One of 'success', 'failure', or 'incomplete'
|
||||
Status string `json:"status"`
|
||||
|
||||
// The structure containing the actual results of the request
|
||||
Data json.RawMessage `json:"data"`
|
||||
|
||||
// The ID of the job that was created in response to a request.
|
||||
JobID int `json:"job_id"`
|
||||
|
||||
// A list of zero or more messages
|
||||
Messages json.RawMessage `json:"msgs"`
|
||||
}
|
||||
|
||||
type credentials struct {
|
||||
Customer string `json:"customer_name"`
|
||||
User string `json:"user_name"`
|
||||
Pass string `json:"password"`
|
||||
}
|
||||
|
||||
type session struct {
|
||||
Token string `json:"token"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
type publish struct {
|
||||
Publish bool `json:"publish"`
|
||||
Notes string `json:"notes"`
|
||||
}
|
||||
|
||||
// Starts a new Dyn API Session. Authenticates using customerName, userName,
|
||||
// password and receives a token to be used in for subsequent requests.
|
||||
func (d *DNSProvider) login() error {
|
||||
payload := &credentials{Customer: d.config.CustomerName, User: d.config.UserName, Pass: d.config.Password}
|
||||
dynRes, err := d.sendRequest(http.MethodPost, "Session", payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var s session
|
||||
err = json.Unmarshal(dynRes.Data, &s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.token = s.Token
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Destroys Dyn Session
|
||||
func (d *DNSProvider) logout() error {
|
||||
if len(d.token) == 0 {
|
||||
// nothing to do
|
||||
return nil
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("%s/Session", defaultBaseURL)
|
||||
req, err := http.NewRequest(http.MethodDelete, url, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Auth-Token", d.token)
|
||||
|
||||
resp, err := d.config.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("API request failed to delete session with HTTP status code %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
d.token = ""
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) publish(zone, notes string) error {
|
||||
pub := &publish{Publish: true, Notes: notes}
|
||||
resource := fmt.Sprintf("Zone/%s/", zone)
|
||||
|
||||
_, err := d.sendRequest(http.MethodPut, resource, pub)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *DNSProvider) sendRequest(method, resource string, payload interface{}) (*dynResponse, error) {
|
||||
url := fmt.Sprintf("%s/%s", defaultBaseURL, resource)
|
||||
|
||||
body, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, url, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
if len(d.token) > 0 {
|
||||
req.Header.Set("Auth-Token", d.token)
|
||||
}
|
||||
|
||||
resp, err := d.config.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode >= 500 {
|
||||
return nil, fmt.Errorf("API request failed with HTTP status code %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var dynRes dynResponse
|
||||
err = json.NewDecoder(resp.Body).Decode(&dynRes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode >= 400 {
|
||||
return nil, fmt.Errorf("API request failed with HTTP status code %d: %s", resp.StatusCode, dynRes.Messages)
|
||||
} else if resp.StatusCode == 307 {
|
||||
// TODO add support for HTTP 307 response and long running jobs
|
||||
return nil, fmt.Errorf("API request returned HTTP 307. This is currently unsupported")
|
||||
}
|
||||
|
||||
if dynRes.Status == "failure" {
|
||||
// TODO add better error handling
|
||||
return nil, fmt.Errorf("API request failed: %s", dynRes.Messages)
|
||||
}
|
||||
|
||||
return &dynRes, nil
|
||||
}
|
157
vendor/github.com/go-acme/lego/providers/dns/dyn/dyn.go
generated
vendored
Normal file
157
vendor/github.com/go-acme/lego/providers/dns/dyn/dyn.go
generated
vendored
Normal file
|
@ -0,0 +1,157 @@
|
|||
// Package dyn implements a DNS provider for solving the DNS-01 challenge using Dyn Managed DNS.
|
||||
package dyn
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
CustomerName string
|
||||
UserName string
|
||||
Password string
|
||||
HTTPClient *http.Client
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt("DYN_TTL", dns01.DefaultTTL),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("DYN_PROPAGATION_TIMEOUT", dns01.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("DYN_POLLING_INTERVAL", dns01.DefaultPollingInterval),
|
||||
HTTPClient: &http.Client{
|
||||
Timeout: env.GetOrDefaultSecond("DYN_HTTP_TIMEOUT", 10*time.Second),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface that uses
|
||||
// Dyn's Managed DNS API to manage TXT records for a domain.
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
token string
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for Dyn DNS.
|
||||
// Credentials must be passed in the environment variables:
|
||||
// DYN_CUSTOMER_NAME, DYN_USER_NAME and DYN_PASSWORD.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("DYN_CUSTOMER_NAME", "DYN_USER_NAME", "DYN_PASSWORD")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("dyn: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.CustomerName = values["DYN_CUSTOMER_NAME"]
|
||||
config.UserName = values["DYN_USER_NAME"]
|
||||
config.Password = values["DYN_PASSWORD"]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for Dyn DNS
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("dyn: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if config.CustomerName == "" || config.UserName == "" || config.Password == "" {
|
||||
return nil, fmt.Errorf("dyn: credentials missing")
|
||||
}
|
||||
|
||||
return &DNSProvider{config: config}, nil
|
||||
}
|
||||
|
||||
// Present creates a TXT record using the specified parameters
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
authZone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dyn: %v", err)
|
||||
}
|
||||
|
||||
err = d.login()
|
||||
if err != nil {
|
||||
return fmt.Errorf("dyn: %v", err)
|
||||
}
|
||||
|
||||
data := map[string]interface{}{
|
||||
"rdata": map[string]string{
|
||||
"txtdata": value,
|
||||
},
|
||||
"ttl": strconv.Itoa(d.config.TTL),
|
||||
}
|
||||
|
||||
resource := fmt.Sprintf("TXTRecord/%s/%s/", authZone, fqdn)
|
||||
_, err = d.sendRequest(http.MethodPost, resource, data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dyn: %v", err)
|
||||
}
|
||||
|
||||
err = d.publish(authZone, "Added TXT record for ACME dns-01 challenge using lego client")
|
||||
if err != nil {
|
||||
return fmt.Errorf("dyn: %v", err)
|
||||
}
|
||||
|
||||
return d.logout()
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
authZone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dyn: %v", err)
|
||||
}
|
||||
|
||||
err = d.login()
|
||||
if err != nil {
|
||||
return fmt.Errorf("dyn: %v", err)
|
||||
}
|
||||
|
||||
resource := fmt.Sprintf("TXTRecord/%s/%s/", authZone, fqdn)
|
||||
url := fmt.Sprintf("%s/%s", defaultBaseURL, resource)
|
||||
|
||||
req, err := http.NewRequest(http.MethodDelete, url, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dyn: %v", err)
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Auth-Token", d.token)
|
||||
|
||||
resp, err := d.config.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dyn: %v", err)
|
||||
}
|
||||
resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("dyn: API request failed to delete TXT record HTTP status code %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
err = d.publish(authZone, "Removed TXT record for ACME dns-01 challenge using lego client")
|
||||
if err != nil {
|
||||
return fmt.Errorf("dyn: %v", err)
|
||||
}
|
||||
|
||||
return d.logout()
|
||||
}
|
||||
|
||||
// Timeout returns the timeout and interval to use when checking for DNS propagation.
|
||||
// Adjusting here to cope with spikes in propagation times.
|
||||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
107
vendor/github.com/go-acme/lego/providers/dns/exec/exec.go
generated
vendored
Normal file
107
vendor/github.com/go-acme/lego/providers/dns/exec/exec.go
generated
vendored
Normal file
|
@ -0,0 +1,107 @@
|
|||
// Package exec implements a DNS provider which runs a program for adding/removing the DNS record.
|
||||
package exec
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/log"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
)
|
||||
|
||||
// Config Provider configuration.
|
||||
type Config struct {
|
||||
Program string
|
||||
Mode string
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
PropagationTimeout: env.GetOrDefaultSecond("EXEC_PROPAGATION_TIMEOUT", dns01.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("EXEC_POLLING_INTERVAL", dns01.DefaultPollingInterval),
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider adds and removes the record for the DNS challenge by calling a
|
||||
// program with command-line parameters.
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a new DNS provider which runs the program in the
|
||||
// environment variable EXEC_PATH for adding and removing the DNS record.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("EXEC_PATH")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("exec: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.Program = values["EXEC_PATH"]
|
||||
config.Mode = os.Getenv("EXEC_MODE")
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig returns a new DNS provider which runs the given configuration
|
||||
// for adding and removing the DNS record.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("the configuration is nil")
|
||||
}
|
||||
|
||||
return &DNSProvider{config: config}, nil
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge.
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
var args []string
|
||||
if d.config.Mode == "RAW" {
|
||||
args = []string{"present", "--", domain, token, keyAuth}
|
||||
} else {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
args = []string{"present", fqdn, value}
|
||||
}
|
||||
|
||||
cmd := exec.Command(d.config.Program, args...)
|
||||
|
||||
output, err := cmd.CombinedOutput()
|
||||
if len(output) > 0 {
|
||||
log.Println(string(output))
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
var args []string
|
||||
if d.config.Mode == "RAW" {
|
||||
args = []string{"cleanup", "--", domain, token, keyAuth}
|
||||
} else {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
args = []string{"cleanup", fqdn, value}
|
||||
}
|
||||
|
||||
cmd := exec.Command(d.config.Program, args...)
|
||||
|
||||
output, err := cmd.CombinedOutput()
|
||||
if len(output) > 0 {
|
||||
log.Println(string(output))
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Timeout returns the timeout and interval to use when checking for DNS propagation.
|
||||
// Adjusting here to cope with spikes in propagation times.
|
||||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
184
vendor/github.com/go-acme/lego/providers/dns/exoscale/exoscale.go
generated
vendored
Normal file
184
vendor/github.com/go-acme/lego/providers/dns/exoscale/exoscale.go
generated
vendored
Normal file
|
@ -0,0 +1,184 @@
|
|||
// Package exoscale implements a DNS provider for solving the DNS-01 challenge using exoscale DNS.
|
||||
package exoscale
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/exoscale/egoscale"
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
)
|
||||
|
||||
const defaultBaseURL = "https://api.exoscale.com/dns"
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
APIKey string
|
||||
APISecret string
|
||||
Endpoint string
|
||||
HTTPClient *http.Client
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt("EXOSCALE_TTL", dns01.DefaultTTL),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("EXOSCALE_PROPAGATION_TIMEOUT", dns01.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("EXOSCALE_POLLING_INTERVAL", dns01.DefaultPollingInterval),
|
||||
HTTPClient: &http.Client{
|
||||
Timeout: env.GetOrDefaultSecond("EXOSCALE_HTTP_TIMEOUT", 0),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface.
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
client *egoscale.Client
|
||||
}
|
||||
|
||||
// NewDNSProvider Credentials must be passed in the environment variables:
|
||||
// EXOSCALE_API_KEY, EXOSCALE_API_SECRET, EXOSCALE_ENDPOINT.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("EXOSCALE_API_KEY", "EXOSCALE_API_SECRET")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("exoscale: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.APIKey = values["EXOSCALE_API_KEY"]
|
||||
config.APISecret = values["EXOSCALE_API_SECRET"]
|
||||
config.Endpoint = env.GetOrFile("EXOSCALE_ENDPOINT")
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for Exoscale.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if config.APIKey == "" || config.APISecret == "" {
|
||||
return nil, fmt.Errorf("exoscale: credentials missing")
|
||||
}
|
||||
|
||||
if config.Endpoint == "" {
|
||||
config.Endpoint = defaultBaseURL
|
||||
}
|
||||
|
||||
client := egoscale.NewClient(config.Endpoint, config.APIKey, config.APISecret)
|
||||
client.HTTPClient = config.HTTPClient
|
||||
|
||||
return &DNSProvider{client: client, config: config}, nil
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge.
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
ctx := context.Background()
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
zone, recordName, err := d.FindZoneAndRecordName(fqdn, domain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
recordID, err := d.FindExistingRecordID(zone, recordName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if recordID == 0 {
|
||||
record := egoscale.DNSRecord{
|
||||
Name: recordName,
|
||||
TTL: d.config.TTL,
|
||||
Content: value,
|
||||
RecordType: "TXT",
|
||||
}
|
||||
|
||||
_, err := d.client.CreateRecord(ctx, zone, record)
|
||||
if err != nil {
|
||||
return errors.New("Error while creating DNS record: " + err.Error())
|
||||
}
|
||||
} else {
|
||||
record := egoscale.UpdateDNSRecord{
|
||||
ID: recordID,
|
||||
Name: recordName,
|
||||
TTL: d.config.TTL,
|
||||
Content: value,
|
||||
RecordType: "TXT",
|
||||
}
|
||||
|
||||
_, err := d.client.UpdateRecord(ctx, zone, record)
|
||||
if err != nil {
|
||||
return errors.New("Error while updating DNS record: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the record matching the specified parameters.
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
ctx := context.Background()
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
zone, recordName, err := d.FindZoneAndRecordName(fqdn, domain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
recordID, err := d.FindExistingRecordID(zone, recordName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if recordID != 0 {
|
||||
err = d.client.DeleteRecord(ctx, zone, recordID)
|
||||
if err != nil {
|
||||
return errors.New("Error while deleting DNS record: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Timeout returns the timeout and interval to use when checking for DNS propagation.
|
||||
// Adjusting here to cope with spikes in propagation times.
|
||||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
// FindExistingRecordID Query Exoscale to find an existing record for this name.
|
||||
// Returns nil if no record could be found
|
||||
func (d *DNSProvider) FindExistingRecordID(zone, recordName string) (int64, error) {
|
||||
ctx := context.Background()
|
||||
records, err := d.client.GetRecords(ctx, zone)
|
||||
if err != nil {
|
||||
return -1, errors.New("Error while retrievening DNS records: " + err.Error())
|
||||
}
|
||||
for _, record := range records {
|
||||
if record.Name == recordName {
|
||||
return record.ID, nil
|
||||
}
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// FindZoneAndRecordName Extract DNS zone and DNS entry name
|
||||
func (d *DNSProvider) FindZoneAndRecordName(fqdn, domain string) (string, string, error) {
|
||||
zone, err := dns01.FindZoneByFqdn(dns01.ToFqdn(domain))
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
zone = dns01.UnFqdn(zone)
|
||||
name := dns01.UnFqdn(fqdn)
|
||||
name = name[:len(name)-len("."+zone)]
|
||||
|
||||
return zone, name, nil
|
||||
}
|
158
vendor/github.com/go-acme/lego/providers/dns/fastdns/fastdns.go
generated
vendored
Normal file
158
vendor/github.com/go-acme/lego/providers/dns/fastdns/fastdns.go
generated
vendored
Normal file
|
@ -0,0 +1,158 @@
|
|||
// Package fastdns implements a DNS provider for solving the DNS-01 challenge using FastDNS.
|
||||
package fastdns
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
configdns "github.com/akamai/AkamaiOPEN-edgegrid-golang/configdns-v1"
|
||||
"github.com/akamai/AkamaiOPEN-edgegrid-golang/edgegrid"
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
edgegrid.Config
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
PropagationTimeout: env.GetOrDefaultSecond("AKAMAI_PROPAGATION_TIMEOUT", dns01.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("AKAMAI_POLLING_INTERVAL", dns01.DefaultPollingInterval),
|
||||
TTL: env.GetOrDefaultInt("AKAMAI_TTL", dns01.DefaultTTL),
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface.
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
}
|
||||
|
||||
// NewDNSProvider uses the supplied environment variables to return a DNSProvider instance:
|
||||
// AKAMAI_HOST, AKAMAI_CLIENT_TOKEN, AKAMAI_CLIENT_SECRET, AKAMAI_ACCESS_TOKEN
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("AKAMAI_HOST", "AKAMAI_CLIENT_TOKEN", "AKAMAI_CLIENT_SECRET", "AKAMAI_ACCESS_TOKEN")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fastdns: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.Config = edgegrid.Config{
|
||||
Host: values["AKAMAI_HOST"],
|
||||
ClientToken: values["AKAMAI_CLIENT_TOKEN"],
|
||||
ClientSecret: values["AKAMAI_CLIENT_SECRET"],
|
||||
AccessToken: values["AKAMAI_ACCESS_TOKEN"],
|
||||
MaxBody: 131072,
|
||||
}
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for FastDNS.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("fastdns: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if config.ClientToken == "" || config.ClientSecret == "" || config.AccessToken == "" || config.Host == "" {
|
||||
return nil, fmt.Errorf("fastdns: credentials are missing")
|
||||
}
|
||||
|
||||
return &DNSProvider{config: config}, nil
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fullfil the dns-01 challenge.
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
zoneName, recordName, err := d.findZoneAndRecordName(fqdn, domain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("fastdns: %v", err)
|
||||
}
|
||||
|
||||
configdns.Init(d.config.Config)
|
||||
|
||||
zone, err := configdns.GetZone(zoneName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("fastdns: %v", err)
|
||||
}
|
||||
|
||||
record := configdns.NewTxtRecord()
|
||||
_ = record.SetField("name", recordName)
|
||||
_ = record.SetField("ttl", d.config.TTL)
|
||||
_ = record.SetField("target", value)
|
||||
_ = record.SetField("active", true)
|
||||
|
||||
for _, r := range zone.Zone.Txt {
|
||||
if r != nil && reflect.DeepEqual(r.ToMap(), record.ToMap()) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return d.createRecord(zone, record)
|
||||
}
|
||||
|
||||
// CleanUp removes the record matching the specified parameters.
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
zoneName, recordName, err := d.findZoneAndRecordName(fqdn, domain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("fastdns: %v", err)
|
||||
}
|
||||
|
||||
configdns.Init(d.config.Config)
|
||||
|
||||
zone, err := configdns.GetZone(zoneName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("fastdns: %v", err)
|
||||
}
|
||||
|
||||
var removed bool
|
||||
for _, r := range zone.Zone.Txt {
|
||||
if r != nil && r.Name == recordName {
|
||||
if zone.RemoveRecord(r) != nil {
|
||||
return fmt.Errorf("fastdns: %v", err)
|
||||
}
|
||||
removed = true
|
||||
}
|
||||
}
|
||||
|
||||
if removed {
|
||||
return zone.Save()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Timeout returns the timeout and interval to use when checking for DNS propagation.
|
||||
// Adjusting here to cope with spikes in propagation times.
|
||||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
func (d *DNSProvider) findZoneAndRecordName(fqdn, domain string) (string, string, error) {
|
||||
zone, err := dns01.FindZoneByFqdn(dns01.ToFqdn(domain))
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
zone = dns01.UnFqdn(zone)
|
||||
name := dns01.UnFqdn(fqdn)
|
||||
name = name[:len(name)-len("."+zone)]
|
||||
|
||||
return zone, name, nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) createRecord(zone *configdns.Zone, record *configdns.TxtRecord) error {
|
||||
err := zone.AddRecord(record)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return zone.Save()
|
||||
}
|
316
vendor/github.com/go-acme/lego/providers/dns/gandi/client.go
generated
vendored
Normal file
316
vendor/github.com/go-acme/lego/providers/dns/gandi/client.go
generated
vendored
Normal file
|
@ -0,0 +1,316 @@
|
|||
package gandi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
// types for XML-RPC method calls and parameters
|
||||
|
||||
type param interface {
|
||||
param()
|
||||
}
|
||||
|
||||
type paramString struct {
|
||||
XMLName xml.Name `xml:"param"`
|
||||
Value string `xml:"value>string"`
|
||||
}
|
||||
|
||||
type paramInt struct {
|
||||
XMLName xml.Name `xml:"param"`
|
||||
Value int `xml:"value>int"`
|
||||
}
|
||||
|
||||
type structMember interface {
|
||||
structMember()
|
||||
}
|
||||
type structMemberString struct {
|
||||
Name string `xml:"name"`
|
||||
Value string `xml:"value>string"`
|
||||
}
|
||||
type structMemberInt struct {
|
||||
Name string `xml:"name"`
|
||||
Value int `xml:"value>int"`
|
||||
}
|
||||
type paramStruct struct {
|
||||
XMLName xml.Name `xml:"param"`
|
||||
StructMembers []structMember `xml:"value>struct>member"`
|
||||
}
|
||||
|
||||
func (p paramString) param() {}
|
||||
func (p paramInt) param() {}
|
||||
func (m structMemberString) structMember() {}
|
||||
func (m structMemberInt) structMember() {}
|
||||
func (p paramStruct) param() {}
|
||||
|
||||
type methodCall struct {
|
||||
XMLName xml.Name `xml:"methodCall"`
|
||||
MethodName string `xml:"methodName"`
|
||||
Params []param `xml:"params"`
|
||||
}
|
||||
|
||||
// types for XML-RPC responses
|
||||
|
||||
type response interface {
|
||||
faultCode() int
|
||||
faultString() string
|
||||
}
|
||||
|
||||
type responseFault struct {
|
||||
FaultCode int `xml:"fault>value>struct>member>value>int"`
|
||||
FaultString string `xml:"fault>value>struct>member>value>string"`
|
||||
}
|
||||
|
||||
func (r responseFault) faultCode() int { return r.FaultCode }
|
||||
func (r responseFault) faultString() string { return r.FaultString }
|
||||
|
||||
type responseStruct struct {
|
||||
responseFault
|
||||
StructMembers []struct {
|
||||
Name string `xml:"name"`
|
||||
ValueInt int `xml:"value>int"`
|
||||
} `xml:"params>param>value>struct>member"`
|
||||
}
|
||||
|
||||
type responseInt struct {
|
||||
responseFault
|
||||
Value int `xml:"params>param>value>int"`
|
||||
}
|
||||
|
||||
type responseBool struct {
|
||||
responseFault
|
||||
Value bool `xml:"params>param>value>boolean"`
|
||||
}
|
||||
|
||||
type rpcError struct {
|
||||
faultCode int
|
||||
faultString string
|
||||
}
|
||||
|
||||
func (e rpcError) Error() string {
|
||||
return fmt.Sprintf("Gandi DNS: RPC Error: (%d) %s", e.faultCode, e.faultString)
|
||||
}
|
||||
|
||||
// rpcCall makes an XML-RPC call to Gandi's RPC endpoint by
|
||||
// marshaling the data given in the call argument to XML and sending
|
||||
// that via HTTP Post to Gandi.
|
||||
// The response is then unmarshalled into the resp argument.
|
||||
func (d *DNSProvider) rpcCall(call *methodCall, resp response) error {
|
||||
// marshal
|
||||
b, err := xml.MarshalIndent(call, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal error: %v", err)
|
||||
}
|
||||
|
||||
// post
|
||||
b = append([]byte(`<?xml version="1.0"?>`+"\n"), b...)
|
||||
respBody, err := d.httpPost(d.config.BaseURL, "text/xml", bytes.NewReader(b))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// unmarshal
|
||||
err = xml.Unmarshal(respBody, resp)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unmarshal error: %v", err)
|
||||
}
|
||||
if resp.faultCode() != 0 {
|
||||
return rpcError{
|
||||
faultCode: resp.faultCode(), faultString: resp.faultString()}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// functions to perform API actions
|
||||
|
||||
func (d *DNSProvider) getZoneID(domain string) (int, error) {
|
||||
resp := &responseStruct{}
|
||||
err := d.rpcCall(&methodCall{
|
||||
MethodName: "domain.info",
|
||||
Params: []param{
|
||||
paramString{Value: d.config.APIKey},
|
||||
paramString{Value: domain},
|
||||
},
|
||||
}, resp)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var zoneID int
|
||||
for _, member := range resp.StructMembers {
|
||||
if member.Name == "zone_id" {
|
||||
zoneID = member.ValueInt
|
||||
}
|
||||
}
|
||||
|
||||
if zoneID == 0 {
|
||||
return 0, fmt.Errorf("could not determine zone_id for %s", domain)
|
||||
}
|
||||
return zoneID, nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) cloneZone(zoneID int, name string) (int, error) {
|
||||
resp := &responseStruct{}
|
||||
err := d.rpcCall(&methodCall{
|
||||
MethodName: "domain.zone.clone",
|
||||
Params: []param{
|
||||
paramString{Value: d.config.APIKey},
|
||||
paramInt{Value: zoneID},
|
||||
paramInt{Value: 0},
|
||||
paramStruct{
|
||||
StructMembers: []structMember{
|
||||
structMemberString{
|
||||
Name: "name",
|
||||
Value: name,
|
||||
}},
|
||||
},
|
||||
},
|
||||
}, resp)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var newZoneID int
|
||||
for _, member := range resp.StructMembers {
|
||||
if member.Name == "id" {
|
||||
newZoneID = member.ValueInt
|
||||
}
|
||||
}
|
||||
|
||||
if newZoneID == 0 {
|
||||
return 0, fmt.Errorf("could not determine cloned zone_id")
|
||||
}
|
||||
return newZoneID, nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) newZoneVersion(zoneID int) (int, error) {
|
||||
resp := &responseInt{}
|
||||
err := d.rpcCall(&methodCall{
|
||||
MethodName: "domain.zone.version.new",
|
||||
Params: []param{
|
||||
paramString{Value: d.config.APIKey},
|
||||
paramInt{Value: zoneID},
|
||||
},
|
||||
}, resp)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if resp.Value == 0 {
|
||||
return 0, fmt.Errorf("could not create new zone version")
|
||||
}
|
||||
return resp.Value, nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) addTXTRecord(zoneID int, version int, name string, value string, ttl int) error {
|
||||
resp := &responseStruct{}
|
||||
err := d.rpcCall(&methodCall{
|
||||
MethodName: "domain.zone.record.add",
|
||||
Params: []param{
|
||||
paramString{Value: d.config.APIKey},
|
||||
paramInt{Value: zoneID},
|
||||
paramInt{Value: version},
|
||||
paramStruct{
|
||||
StructMembers: []structMember{
|
||||
structMemberString{
|
||||
Name: "type",
|
||||
Value: "TXT",
|
||||
}, structMemberString{
|
||||
Name: "name",
|
||||
Value: name,
|
||||
}, structMemberString{
|
||||
Name: "value",
|
||||
Value: value,
|
||||
}, structMemberInt{
|
||||
Name: "ttl",
|
||||
Value: ttl,
|
||||
}},
|
||||
},
|
||||
},
|
||||
}, resp)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *DNSProvider) setZoneVersion(zoneID int, version int) error {
|
||||
resp := &responseBool{}
|
||||
err := d.rpcCall(&methodCall{
|
||||
MethodName: "domain.zone.version.set",
|
||||
Params: []param{
|
||||
paramString{Value: d.config.APIKey},
|
||||
paramInt{Value: zoneID},
|
||||
paramInt{Value: version},
|
||||
},
|
||||
}, resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !resp.Value {
|
||||
return fmt.Errorf("could not set zone version")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) setZone(domain string, zoneID int) error {
|
||||
resp := &responseStruct{}
|
||||
err := d.rpcCall(&methodCall{
|
||||
MethodName: "domain.zone.set",
|
||||
Params: []param{
|
||||
paramString{Value: d.config.APIKey},
|
||||
paramString{Value: domain},
|
||||
paramInt{Value: zoneID},
|
||||
},
|
||||
}, resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var respZoneID int
|
||||
for _, member := range resp.StructMembers {
|
||||
if member.Name == "zone_id" {
|
||||
respZoneID = member.ValueInt
|
||||
}
|
||||
}
|
||||
|
||||
if respZoneID != zoneID {
|
||||
return fmt.Errorf("could not set new zone_id for %s", domain)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) deleteZone(zoneID int) error {
|
||||
resp := &responseBool{}
|
||||
err := d.rpcCall(&methodCall{
|
||||
MethodName: "domain.zone.delete",
|
||||
Params: []param{
|
||||
paramString{Value: d.config.APIKey},
|
||||
paramInt{Value: zoneID},
|
||||
},
|
||||
}, resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !resp.Value {
|
||||
return fmt.Errorf("could not delete zone_id")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) httpPost(url string, bodyType string, body io.Reader) ([]byte, error) {
|
||||
resp, err := d.config.HTTPClient.Post(url, bodyType, body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("HTTP Post Error: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("HTTP Post Error: %v", err)
|
||||
}
|
||||
|
||||
return b, nil
|
||||
}
|
214
vendor/github.com/go-acme/lego/providers/dns/gandi/gandi.go
generated
vendored
Normal file
214
vendor/github.com/go-acme/lego/providers/dns/gandi/gandi.go
generated
vendored
Normal file
|
@ -0,0 +1,214 @@
|
|||
// Package gandi implements a DNS provider for solving the DNS-01 challenge using Gandi DNS.
|
||||
package gandi
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
)
|
||||
|
||||
// Gandi API reference: http://doc.rpc.gandi.net/index.html
|
||||
// Gandi API domain examples: http://doc.rpc.gandi.net/domain/faq.html
|
||||
|
||||
const (
|
||||
// defaultBaseURL Gandi XML-RPC endpoint used by Present and CleanUp
|
||||
defaultBaseURL = "https://rpc.gandi.net/xmlrpc/"
|
||||
minTTL = 300
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
BaseURL string
|
||||
APIKey string
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt("GANDI_TTL", minTTL),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("GANDI_PROPAGATION_TIMEOUT", 40*time.Minute),
|
||||
PollingInterval: env.GetOrDefaultSecond("GANDI_POLLING_INTERVAL", 60*time.Second),
|
||||
HTTPClient: &http.Client{
|
||||
Timeout: env.GetOrDefaultSecond("GANDI_HTTP_TIMEOUT", 60*time.Second),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// inProgressInfo contains information about an in-progress challenge
|
||||
type inProgressInfo struct {
|
||||
zoneID int // zoneID of gandi zone to restore in CleanUp
|
||||
newZoneID int // zoneID of temporary gandi zone containing TXT record
|
||||
authZone string // the domain name registered at gandi with trailing "."
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the
|
||||
// acme.ChallengeProviderTimeout interface that uses Gandi's XML-RPC
|
||||
// API to manage TXT records for a domain.
|
||||
type DNSProvider struct {
|
||||
inProgressFQDNs map[string]inProgressInfo
|
||||
inProgressAuthZones map[string]struct{}
|
||||
inProgressMu sync.Mutex
|
||||
config *Config
|
||||
// findZoneByFqdn determines the DNS zone of an fqdn. It is overridden during tests.
|
||||
findZoneByFqdn func(fqdn string) (string, error)
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for Gandi.
|
||||
// Credentials must be passed in the environment variable: GANDI_API_KEY.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("GANDI_API_KEY")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("gandi: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.APIKey = values["GANDI_API_KEY"]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for Gandi.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("gandi: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if config.APIKey == "" {
|
||||
return nil, fmt.Errorf("gandi: no API Key given")
|
||||
}
|
||||
|
||||
if config.BaseURL == "" {
|
||||
config.BaseURL = defaultBaseURL
|
||||
}
|
||||
|
||||
return &DNSProvider{
|
||||
config: config,
|
||||
inProgressFQDNs: make(map[string]inProgressInfo),
|
||||
inProgressAuthZones: make(map[string]struct{}),
|
||||
findZoneByFqdn: dns01.FindZoneByFqdn,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Present creates a TXT record using the specified parameters. It
|
||||
// does this by creating and activating a new temporary Gandi DNS
|
||||
// zone. This new zone contains the TXT record.
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
if d.config.TTL < minTTL {
|
||||
d.config.TTL = minTTL // 300 is gandi minimum value for ttl
|
||||
}
|
||||
|
||||
// find authZone and Gandi zone_id for fqdn
|
||||
authZone, err := d.findZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("gandi: findZoneByFqdn failure: %v", err)
|
||||
}
|
||||
|
||||
zoneID, err := d.getZoneID(authZone)
|
||||
if err != nil {
|
||||
return fmt.Errorf("gandi: %v", err)
|
||||
}
|
||||
|
||||
// determine name of TXT record
|
||||
if !strings.HasSuffix(
|
||||
strings.ToLower(fqdn), strings.ToLower("."+authZone)) {
|
||||
return fmt.Errorf("gandi: unexpected authZone %s for fqdn %s", authZone, fqdn)
|
||||
}
|
||||
name := fqdn[:len(fqdn)-len("."+authZone)]
|
||||
|
||||
// acquire lock and check there is not a challenge already in
|
||||
// progress for this value of authZone
|
||||
d.inProgressMu.Lock()
|
||||
defer d.inProgressMu.Unlock()
|
||||
|
||||
if _, ok := d.inProgressAuthZones[authZone]; ok {
|
||||
return fmt.Errorf("gandi: challenge already in progress for authZone %s", authZone)
|
||||
}
|
||||
|
||||
// perform API actions to create and activate new gandi zone
|
||||
// containing the required TXT record
|
||||
newZoneName := fmt.Sprintf("%s [ACME Challenge %s]", dns01.UnFqdn(authZone), time.Now().Format(time.RFC822Z))
|
||||
|
||||
newZoneID, err := d.cloneZone(zoneID, newZoneName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
newZoneVersion, err := d.newZoneVersion(newZoneID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("gandi: %v", err)
|
||||
}
|
||||
|
||||
err = d.addTXTRecord(newZoneID, newZoneVersion, name, value, d.config.TTL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("gandi: %v", err)
|
||||
}
|
||||
|
||||
err = d.setZoneVersion(newZoneID, newZoneVersion)
|
||||
if err != nil {
|
||||
return fmt.Errorf("gandi: %v", err)
|
||||
}
|
||||
|
||||
err = d.setZone(authZone, newZoneID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("gandi: %v", err)
|
||||
}
|
||||
|
||||
// save data necessary for CleanUp
|
||||
d.inProgressFQDNs[fqdn] = inProgressInfo{
|
||||
zoneID: zoneID,
|
||||
newZoneID: newZoneID,
|
||||
authZone: authZone,
|
||||
}
|
||||
d.inProgressAuthZones[authZone] = struct{}{}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified
|
||||
// parameters. It does this by restoring the old Gandi DNS zone and
|
||||
// removing the temporary one created by Present.
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
// acquire lock and retrieve zoneID, newZoneID and authZone
|
||||
d.inProgressMu.Lock()
|
||||
defer d.inProgressMu.Unlock()
|
||||
|
||||
if _, ok := d.inProgressFQDNs[fqdn]; !ok {
|
||||
// if there is no cleanup information then just return
|
||||
return nil
|
||||
}
|
||||
|
||||
zoneID := d.inProgressFQDNs[fqdn].zoneID
|
||||
newZoneID := d.inProgressFQDNs[fqdn].newZoneID
|
||||
authZone := d.inProgressFQDNs[fqdn].authZone
|
||||
delete(d.inProgressFQDNs, fqdn)
|
||||
delete(d.inProgressAuthZones, authZone)
|
||||
|
||||
// perform API actions to restore old gandi zone for authZone
|
||||
err := d.setZone(authZone, zoneID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("gandi: %v", err)
|
||||
}
|
||||
|
||||
return d.deleteZone(newZoneID)
|
||||
}
|
||||
|
||||
// Timeout returns the values (40*time.Minute, 60*time.Second) which
|
||||
// are used by the acme package as timeout and check interval values
|
||||
// when checking for DNS record propagation with Gandi.
|
||||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
199
vendor/github.com/go-acme/lego/providers/dns/gandiv5/client.go
generated
vendored
Normal file
199
vendor/github.com/go-acme/lego/providers/dns/gandiv5/client.go
generated
vendored
Normal file
|
@ -0,0 +1,199 @@
|
|||
package gandiv5
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-acme/lego/log"
|
||||
)
|
||||
|
||||
const apiKeyHeader = "X-Api-Key"
|
||||
|
||||
// types for JSON responses with only a message
|
||||
type apiResponse struct {
|
||||
Message string `json:"message"`
|
||||
UUID string `json:"uuid,omitempty"`
|
||||
}
|
||||
|
||||
// Record TXT record representation
|
||||
type Record struct {
|
||||
RRSetTTL int `json:"rrset_ttl"`
|
||||
RRSetValues []string `json:"rrset_values"`
|
||||
RRSetName string `json:"rrset_name,omitempty"`
|
||||
RRSetType string `json:"rrset_type,omitempty"`
|
||||
}
|
||||
|
||||
func (d *DNSProvider) addTXTRecord(domain string, name string, value string, ttl int) error {
|
||||
// Get exiting values for the TXT records
|
||||
// Needed to create challenges for both wildcard and base name domains
|
||||
txtRecord, err := d.getTXTRecord(domain, name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
values := []string{value}
|
||||
if len(txtRecord.RRSetValues) > 0 {
|
||||
values = append(values, txtRecord.RRSetValues...)
|
||||
}
|
||||
|
||||
target := fmt.Sprintf("domains/%s/records/%s/TXT", domain, name)
|
||||
|
||||
newRecord := &Record{RRSetTTL: ttl, RRSetValues: values}
|
||||
req, err := d.newRequest(http.MethodPut, target, newRecord)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
message := &apiResponse{}
|
||||
err = d.do(req, message)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create TXT record for domain %s and name %s: %v", domain, name, err)
|
||||
}
|
||||
|
||||
if message != nil && len(message.Message) > 0 {
|
||||
log.Infof("API response: %s", message.Message)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) getTXTRecord(domain, name string) (*Record, error) {
|
||||
target := fmt.Sprintf("domains/%s/records/%s/TXT", domain, name)
|
||||
|
||||
// Get exiting values for the TXT records
|
||||
// Needed to create challenges for both wildcard and base name domains
|
||||
req, err := d.newRequest(http.MethodGet, target, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
txtRecord := &Record{}
|
||||
err = d.do(req, txtRecord)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get TXT records for domain %s and name %s: %v", domain, name, err)
|
||||
}
|
||||
|
||||
return txtRecord, nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) deleteTXTRecord(domain string, name string) error {
|
||||
target := fmt.Sprintf("domains/%s/records/%s/TXT", domain, name)
|
||||
|
||||
req, err := d.newRequest(http.MethodDelete, target, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
message := &apiResponse{}
|
||||
err = d.do(req, message)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to delete TXT record for domain %s and name %s: %v", domain, name, err)
|
||||
}
|
||||
|
||||
if message != nil && len(message.Message) > 0 {
|
||||
log.Infof("API response: %s", message.Message)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) newRequest(method, resource string, body interface{}) (*http.Request, error) {
|
||||
u := fmt.Sprintf("%s/%s", d.config.BaseURL, resource)
|
||||
|
||||
if body == nil {
|
||||
req, err := http.NewRequest(method, u, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
reqBody, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, u, bytes.NewBuffer(reqBody))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) do(req *http.Request, v interface{}) error {
|
||||
if len(d.config.APIKey) > 0 {
|
||||
req.Header.Set(apiKeyHeader, d.config.APIKey)
|
||||
}
|
||||
|
||||
resp, err := d.config.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = checkResponse(resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
raw, err := readBody(resp)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read body: %v", err)
|
||||
}
|
||||
|
||||
if len(raw) > 0 {
|
||||
err = json.Unmarshal(raw, v)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unmarshaling error: %v: %s", err, string(raw))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkResponse(resp *http.Response) error {
|
||||
if resp.StatusCode == 404 && resp.Request.Method == http.MethodGet {
|
||||
return nil
|
||||
}
|
||||
|
||||
if resp.StatusCode >= 400 {
|
||||
data, err := readBody(resp)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%d [%s] request failed: %v", resp.StatusCode, http.StatusText(resp.StatusCode), err)
|
||||
}
|
||||
|
||||
message := &apiResponse{}
|
||||
err = json.Unmarshal(data, message)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%d [%s] request failed: %v: %s", resp.StatusCode, http.StatusText(resp.StatusCode), err, data)
|
||||
}
|
||||
return fmt.Errorf("%d [%s] request failed: %s", resp.StatusCode, http.StatusText(resp.StatusCode), message.Message)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func readBody(resp *http.Response) ([]byte, error) {
|
||||
if resp.Body == nil {
|
||||
return nil, fmt.Errorf("response body is nil")
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
rawBody, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return rawBody, nil
|
||||
}
|
167
vendor/github.com/go-acme/lego/providers/dns/gandiv5/gandiv5.go
generated
vendored
Normal file
167
vendor/github.com/go-acme/lego/providers/dns/gandiv5/gandiv5.go
generated
vendored
Normal file
|
@ -0,0 +1,167 @@
|
|||
// Package gandiv5 implements a DNS provider for solving the DNS-01 challenge using Gandi LiveDNS api.
|
||||
package gandiv5
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
)
|
||||
|
||||
// Gandi API reference: http://doc.livedns.gandi.net/
|
||||
|
||||
const (
|
||||
// defaultBaseURL endpoint is the Gandi API endpoint used by Present and CleanUp.
|
||||
defaultBaseURL = "https://dns.api.gandi.net/api/v5"
|
||||
minTTL = 300
|
||||
)
|
||||
|
||||
// inProgressInfo contains information about an in-progress challenge
|
||||
type inProgressInfo struct {
|
||||
fieldName string
|
||||
authZone string
|
||||
}
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
BaseURL string
|
||||
APIKey string
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt("GANDIV5_TTL", minTTL),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("GANDIV5_PROPAGATION_TIMEOUT", 20*time.Minute),
|
||||
PollingInterval: env.GetOrDefaultSecond("GANDIV5_POLLING_INTERVAL", 20*time.Second),
|
||||
HTTPClient: &http.Client{
|
||||
Timeout: env.GetOrDefaultSecond("GANDIV5_HTTP_TIMEOUT", 10*time.Second),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the
|
||||
// acme.ChallengeProviderTimeout interface that uses Gandi's LiveDNS
|
||||
// API to manage TXT records for a domain.
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
inProgressFQDNs map[string]inProgressInfo
|
||||
inProgressMu sync.Mutex
|
||||
// findZoneByFqdn determines the DNS zone of an fqdn. It is overridden during tests.
|
||||
findZoneByFqdn func(fqdn string) (string, error)
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for Gandi.
|
||||
// Credentials must be passed in the environment variable: GANDIV5_API_KEY.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("GANDIV5_API_KEY")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("gandi: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.APIKey = values["GANDIV5_API_KEY"]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for Gandi.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("gandiv5: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if config.APIKey == "" {
|
||||
return nil, fmt.Errorf("gandiv5: no API Key given")
|
||||
}
|
||||
|
||||
if config.BaseURL == "" {
|
||||
config.BaseURL = defaultBaseURL
|
||||
}
|
||||
|
||||
if config.TTL < minTTL {
|
||||
return nil, fmt.Errorf("gandiv5: invalid TTL, TTL (%d) must be greater than %d", config.TTL, minTTL)
|
||||
}
|
||||
|
||||
return &DNSProvider{
|
||||
config: config,
|
||||
inProgressFQDNs: make(map[string]inProgressInfo),
|
||||
findZoneByFqdn: dns01.FindZoneByFqdn,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Present creates a TXT record using the specified parameters.
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
// find authZone
|
||||
authZone, err := d.findZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("gandiv5: findZoneByFqdn failure: %v", err)
|
||||
}
|
||||
|
||||
// determine name of TXT record
|
||||
if !strings.HasSuffix(
|
||||
strings.ToLower(fqdn), strings.ToLower("."+authZone)) {
|
||||
return fmt.Errorf("gandiv5: unexpected authZone %s for fqdn %s", authZone, fqdn)
|
||||
}
|
||||
name := fqdn[:len(fqdn)-len("."+authZone)]
|
||||
|
||||
// acquire lock and check there is not a challenge already in
|
||||
// progress for this value of authZone
|
||||
d.inProgressMu.Lock()
|
||||
defer d.inProgressMu.Unlock()
|
||||
|
||||
// add TXT record into authZone
|
||||
err = d.addTXTRecord(dns01.UnFqdn(authZone), name, value, d.config.TTL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// save data necessary for CleanUp
|
||||
d.inProgressFQDNs[fqdn] = inProgressInfo{
|
||||
authZone: authZone,
|
||||
fieldName: name,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters.
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
// acquire lock and retrieve authZone
|
||||
d.inProgressMu.Lock()
|
||||
defer d.inProgressMu.Unlock()
|
||||
if _, ok := d.inProgressFQDNs[fqdn]; !ok {
|
||||
// if there is no cleanup information then just return
|
||||
return nil
|
||||
}
|
||||
|
||||
fieldName := d.inProgressFQDNs[fqdn].fieldName
|
||||
authZone := d.inProgressFQDNs[fqdn].authZone
|
||||
delete(d.inProgressFQDNs, fqdn)
|
||||
|
||||
// delete TXT record from authZone
|
||||
err := d.deleteTXTRecord(dns01.UnFqdn(authZone), fieldName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("gandiv5: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Timeout returns the values (20*time.Minute, 20*time.Second) which
|
||||
// are used by the acme package as timeout and check interval values
|
||||
// when checking for DNS record propagation with Gandi.
|
||||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
327
vendor/github.com/go-acme/lego/providers/dns/gcloud/googlecloud.go
generated
vendored
Normal file
327
vendor/github.com/go-acme/lego/providers/dns/gcloud/googlecloud.go
generated
vendored
Normal file
|
@ -0,0 +1,327 @@
|
|||
// Package gcloud implements a DNS provider for solving the DNS-01 challenge using Google Cloud DNS.
|
||||
package gcloud
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/log"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
"github.com/go-acme/lego/platform/wait"
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/oauth2/google"
|
||||
"google.golang.org/api/dns/v1"
|
||||
"google.golang.org/api/googleapi"
|
||||
)
|
||||
|
||||
const (
|
||||
changeStatusDone = "done"
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
Debug bool
|
||||
Project string
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
Debug: env.GetOrDefaultBool("GCE_DEBUG", false),
|
||||
TTL: env.GetOrDefaultInt("GCE_TTL", dns01.DefaultTTL),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("GCE_PROPAGATION_TIMEOUT", 180*time.Second),
|
||||
PollingInterval: env.GetOrDefaultSecond("GCE_POLLING_INTERVAL", 5*time.Second),
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the DNSProvider interface.
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
client *dns.Service
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for Google Cloud DNS.
|
||||
// Project name must be passed in the environment variable: GCE_PROJECT.
|
||||
// A Service Account can be passed in the environment variable: GCE_SERVICE_ACCOUNT
|
||||
// or by specifying the keyfile location: GCE_SERVICE_ACCOUNT_FILE
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
// Use a service account file if specified via environment variable.
|
||||
if saKey := env.GetOrFile("GCE_SERVICE_ACCOUNT"); len(saKey) > 0 {
|
||||
return NewDNSProviderServiceAccountKey([]byte(saKey))
|
||||
}
|
||||
|
||||
// Use default credentials.
|
||||
project := env.GetOrDefaultString("GCE_PROJECT", "")
|
||||
return NewDNSProviderCredentials(project)
|
||||
}
|
||||
|
||||
// NewDNSProviderCredentials uses the supplied credentials
|
||||
// to return a DNSProvider instance configured for Google Cloud DNS.
|
||||
func NewDNSProviderCredentials(project string) (*DNSProvider, error) {
|
||||
if project == "" {
|
||||
return nil, fmt.Errorf("googlecloud: project name missing")
|
||||
}
|
||||
|
||||
client, err := google.DefaultClient(context.Background(), dns.NdevClouddnsReadwriteScope)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("googlecloud: unable to get Google Cloud client: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.Project = project
|
||||
config.HTTPClient = client
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderServiceAccountKey uses the supplied service account JSON
|
||||
// to return a DNSProvider instance configured for Google Cloud DNS.
|
||||
func NewDNSProviderServiceAccountKey(saKey []byte) (*DNSProvider, error) {
|
||||
if len(saKey) == 0 {
|
||||
return nil, fmt.Errorf("googlecloud: Service Account is missing")
|
||||
}
|
||||
|
||||
// If GCE_PROJECT is non-empty it overrides the project in the service
|
||||
// account file.
|
||||
project := env.GetOrDefaultString("GCE_PROJECT", "")
|
||||
if project == "" {
|
||||
// read project id from service account file
|
||||
var datJSON struct {
|
||||
ProjectID string `json:"project_id"`
|
||||
}
|
||||
err := json.Unmarshal(saKey, &datJSON)
|
||||
if err != nil || datJSON.ProjectID == "" {
|
||||
return nil, fmt.Errorf("googlecloud: project ID not found in Google Cloud Service Account file")
|
||||
}
|
||||
project = datJSON.ProjectID
|
||||
}
|
||||
|
||||
conf, err := google.JWTConfigFromJSON(saKey, dns.NdevClouddnsReadwriteScope)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("googlecloud: unable to acquire config: %v", err)
|
||||
}
|
||||
client := conf.Client(context.Background())
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.Project = project
|
||||
config.HTTPClient = client
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderServiceAccount uses the supplied service account JSON file
|
||||
// to return a DNSProvider instance configured for Google Cloud DNS.
|
||||
func NewDNSProviderServiceAccount(saFile string) (*DNSProvider, error) {
|
||||
if saFile == "" {
|
||||
return nil, fmt.Errorf("googlecloud: Service Account file missing")
|
||||
}
|
||||
|
||||
saKey, err := ioutil.ReadFile(saFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("googlecloud: unable to read Service Account file: %v", err)
|
||||
}
|
||||
|
||||
return NewDNSProviderServiceAccountKey(saKey)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for Google Cloud DNS.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("googlecloud: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
svc, err := dns.New(config.HTTPClient)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("googlecloud: unable to create Google Cloud DNS service: %v", err)
|
||||
}
|
||||
|
||||
return &DNSProvider{config: config, client: svc}, nil
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge.
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
zone, err := d.getHostedZone(domain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("googlecloud: %v", err)
|
||||
}
|
||||
|
||||
// Look for existing records.
|
||||
existingRrSet, err := d.findTxtRecords(zone, fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("googlecloud: %v", err)
|
||||
}
|
||||
|
||||
for _, rrSet := range existingRrSet {
|
||||
var rrd []string
|
||||
for _, rr := range rrSet.Rrdatas {
|
||||
data := mustUnquote(rr)
|
||||
rrd = append(rrd, data)
|
||||
|
||||
if data == value {
|
||||
log.Printf("skip: the record already exists: %s", value)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
rrSet.Rrdatas = rrd
|
||||
}
|
||||
|
||||
// Attempt to delete the existing records before adding the new one.
|
||||
if len(existingRrSet) > 0 {
|
||||
if err = d.applyChanges(zone, &dns.Change{Deletions: existingRrSet}); err != nil {
|
||||
return fmt.Errorf("googlecloud: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
rec := &dns.ResourceRecordSet{
|
||||
Name: fqdn,
|
||||
Rrdatas: []string{value},
|
||||
Ttl: int64(d.config.TTL),
|
||||
Type: "TXT",
|
||||
}
|
||||
|
||||
// Append existing TXT record data to the new TXT record data
|
||||
for _, rrSet := range existingRrSet {
|
||||
for _, rr := range rrSet.Rrdatas {
|
||||
if rr != value {
|
||||
rec.Rrdatas = append(rec.Rrdatas, rr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
change := &dns.Change{
|
||||
Additions: []*dns.ResourceRecordSet{rec},
|
||||
}
|
||||
|
||||
if err = d.applyChanges(zone, change); err != nil {
|
||||
return fmt.Errorf("googlecloud: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) applyChanges(zone string, change *dns.Change) error {
|
||||
if d.config.Debug {
|
||||
data, _ := json.Marshal(change)
|
||||
log.Printf("change (Create): %s", string(data))
|
||||
}
|
||||
|
||||
chg, err := d.client.Changes.Create(d.config.Project, zone, change).Do()
|
||||
if err != nil {
|
||||
if v, ok := err.(*googleapi.Error); ok {
|
||||
if v.Code == http.StatusNotFound {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
data, _ := json.Marshal(change)
|
||||
return fmt.Errorf("failed to perform changes [zone %s, change %s]: %v", zone, string(data), err)
|
||||
}
|
||||
|
||||
if chg.Status == changeStatusDone {
|
||||
return nil
|
||||
}
|
||||
|
||||
chgID := chg.Id
|
||||
|
||||
// wait for change to be acknowledged
|
||||
return wait.For("apply change", 30*time.Second, 3*time.Second, func() (bool, error) {
|
||||
if d.config.Debug {
|
||||
data, _ := json.Marshal(change)
|
||||
log.Printf("change (Get): %s", string(data))
|
||||
}
|
||||
|
||||
chg, err = d.client.Changes.Get(d.config.Project, zone, chgID).Do()
|
||||
if err != nil {
|
||||
data, _ := json.Marshal(change)
|
||||
return false, fmt.Errorf("failed to get changes [zone %s, change %s]: %v", zone, string(data), err)
|
||||
}
|
||||
|
||||
if chg.Status == changeStatusDone {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, fmt.Errorf("status: %s", chg.Status)
|
||||
})
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters.
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
zone, err := d.getHostedZone(domain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("googlecloud: %v", err)
|
||||
}
|
||||
|
||||
records, err := d.findTxtRecords(zone, fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("googlecloud: %v", err)
|
||||
}
|
||||
|
||||
if len(records) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err = d.client.Changes.Create(d.config.Project, zone, &dns.Change{Deletions: records}).Do()
|
||||
if err != nil {
|
||||
return fmt.Errorf("googlecloud: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Timeout customizes the timeout values used by the ACME package for checking
|
||||
// DNS record validity.
|
||||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
// getHostedZone returns the managed-zone
|
||||
func (d *DNSProvider) getHostedZone(domain string) (string, error) {
|
||||
authZone, err := dns01.FindZoneByFqdn(dns01.ToFqdn(domain))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
zones, err := d.client.ManagedZones.
|
||||
List(d.config.Project).
|
||||
DnsName(authZone).
|
||||
Do()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("API call failed: %v", err)
|
||||
}
|
||||
|
||||
if len(zones.ManagedZones) == 0 {
|
||||
return "", fmt.Errorf("no matching domain found for domain %s", authZone)
|
||||
}
|
||||
|
||||
return zones.ManagedZones[0].Name, nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) findTxtRecords(zone, fqdn string) ([]*dns.ResourceRecordSet, error) {
|
||||
recs, err := d.client.ResourceRecordSets.List(d.config.Project, zone).Name(fqdn).Type("TXT").Do()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return recs.Rrsets, nil
|
||||
}
|
||||
|
||||
func mustUnquote(raw string) string {
|
||||
clean, err := strconv.Unquote(raw)
|
||||
if err != nil {
|
||||
return raw
|
||||
}
|
||||
return clean
|
||||
}
|
91
vendor/github.com/go-acme/lego/providers/dns/glesys/client.go
generated
vendored
Normal file
91
vendor/github.com/go-acme/lego/providers/dns/glesys/client.go
generated
vendored
Normal file
|
@ -0,0 +1,91 @@
|
|||
package glesys
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-acme/lego/log"
|
||||
)
|
||||
|
||||
// types for JSON method calls, parameters, and responses
|
||||
|
||||
type addRecordRequest struct {
|
||||
DomainName string `json:"domainname"`
|
||||
Host string `json:"host"`
|
||||
Type string `json:"type"`
|
||||
Data string `json:"data"`
|
||||
TTL int `json:"ttl,omitempty"`
|
||||
}
|
||||
|
||||
type deleteRecordRequest struct {
|
||||
RecordID int `json:"recordid"`
|
||||
}
|
||||
|
||||
type responseStruct struct {
|
||||
Response struct {
|
||||
Status struct {
|
||||
Code int `json:"code"`
|
||||
} `json:"status"`
|
||||
Record deleteRecordRequest `json:"record"`
|
||||
} `json:"response"`
|
||||
}
|
||||
|
||||
func (d *DNSProvider) addTXTRecord(fqdn string, domain string, name string, value string, ttl int) (int, error) {
|
||||
response, err := d.sendRequest(http.MethodPost, "addrecord", addRecordRequest{
|
||||
DomainName: domain,
|
||||
Host: name,
|
||||
Type: "TXT",
|
||||
Data: value,
|
||||
TTL: ttl,
|
||||
})
|
||||
|
||||
if response != nil && response.Response.Status.Code == http.StatusOK {
|
||||
log.Infof("[%s]: Successfully created record id %d", fqdn, response.Response.Record.RecordID)
|
||||
return response.Response.Record.RecordID, nil
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
|
||||
func (d *DNSProvider) deleteTXTRecord(fqdn string, recordid int) error {
|
||||
response, err := d.sendRequest(http.MethodPost, "deleterecord", deleteRecordRequest{
|
||||
RecordID: recordid,
|
||||
})
|
||||
if response != nil && response.Response.Status.Code == 200 {
|
||||
log.Infof("[%s]: Successfully deleted record id %d", fqdn, recordid)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *DNSProvider) sendRequest(method string, resource string, payload interface{}) (*responseStruct, error) {
|
||||
url := fmt.Sprintf("%s/%s", defaultBaseURL, resource)
|
||||
|
||||
body, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, url, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.SetBasicAuth(d.config.APIUser, d.config.APIKey)
|
||||
|
||||
resp, err := d.config.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode >= 400 {
|
||||
return nil, fmt.Errorf("request failed with HTTP status code %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var response responseStruct
|
||||
err = json.NewDecoder(resp.Body).Decode(&response)
|
||||
|
||||
return &response, err
|
||||
}
|
146
vendor/github.com/go-acme/lego/providers/dns/glesys/glesys.go
generated
vendored
Normal file
146
vendor/github.com/go-acme/lego/providers/dns/glesys/glesys.go
generated
vendored
Normal file
|
@ -0,0 +1,146 @@
|
|||
// Package glesys implements a DNS provider for solving the DNS-01 challenge using GleSYS api.
|
||||
package glesys
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
)
|
||||
|
||||
const (
|
||||
// defaultBaseURL is the GleSYS API endpoint used by Present and CleanUp.
|
||||
defaultBaseURL = "https://api.glesys.com/domain"
|
||||
minTTL = 60
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
APIUser string
|
||||
APIKey string
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt("GLESYS_TTL", minTTL),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("GLESYS_PROPAGATION_TIMEOUT", 20*time.Minute),
|
||||
PollingInterval: env.GetOrDefaultSecond("GLESYS_POLLING_INTERVAL", 20*time.Second),
|
||||
HTTPClient: &http.Client{
|
||||
Timeout: env.GetOrDefaultSecond("GLESYS_HTTP_TIMEOUT", 10*time.Second),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the
|
||||
// acme.ChallengeProviderTimeout interface that uses GleSYS
|
||||
// API to manage TXT records for a domain.
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
activeRecords map[string]int
|
||||
inProgressMu sync.Mutex
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for GleSYS.
|
||||
// Credentials must be passed in the environment variables:
|
||||
// GLESYS_API_USER and GLESYS_API_KEY.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("GLESYS_API_USER", "GLESYS_API_KEY")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("glesys: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.APIUser = values["GLESYS_API_USER"]
|
||||
config.APIKey = values["GLESYS_API_KEY"]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for GleSYS.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("glesys: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if config.APIUser == "" || config.APIKey == "" {
|
||||
return nil, fmt.Errorf("glesys: incomplete credentials provided")
|
||||
}
|
||||
|
||||
if config.TTL < minTTL {
|
||||
return nil, fmt.Errorf("glesys: invalid TTL, TTL (%d) must be greater than %d", config.TTL, minTTL)
|
||||
}
|
||||
|
||||
return &DNSProvider{
|
||||
config: config,
|
||||
activeRecords: make(map[string]int),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Present creates a TXT record using the specified parameters.
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
// find authZone
|
||||
authZone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("glesys: findZoneByFqdn failure: %v", err)
|
||||
}
|
||||
|
||||
// determine name of TXT record
|
||||
if !strings.HasSuffix(
|
||||
strings.ToLower(fqdn), strings.ToLower("."+authZone)) {
|
||||
return fmt.Errorf("glesys: unexpected authZone %s for fqdn %s", authZone, fqdn)
|
||||
}
|
||||
name := fqdn[:len(fqdn)-len("."+authZone)]
|
||||
|
||||
// acquire lock and check there is not a challenge already in
|
||||
// progress for this value of authZone
|
||||
d.inProgressMu.Lock()
|
||||
defer d.inProgressMu.Unlock()
|
||||
|
||||
// add TXT record into authZone
|
||||
recordID, err := d.addTXTRecord(domain, dns01.UnFqdn(authZone), name, value, d.config.TTL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// save data necessary for CleanUp
|
||||
d.activeRecords[fqdn] = recordID
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters.
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
// acquire lock and retrieve authZone
|
||||
d.inProgressMu.Lock()
|
||||
defer d.inProgressMu.Unlock()
|
||||
if _, ok := d.activeRecords[fqdn]; !ok {
|
||||
// if there is no cleanup information then just return
|
||||
return nil
|
||||
}
|
||||
|
||||
recordID := d.activeRecords[fqdn]
|
||||
delete(d.activeRecords, fqdn)
|
||||
|
||||
// delete TXT record from authZone
|
||||
return d.deleteTXTRecord(domain, recordID)
|
||||
}
|
||||
|
||||
// Timeout returns the values (20*time.Minute, 20*time.Second) which
|
||||
// are used by the acme package as timeout and check interval values
|
||||
// when checking for DNS record propagation with GleSYS.
|
||||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
53
vendor/github.com/go-acme/lego/providers/dns/godaddy/client.go
generated
vendored
Normal file
53
vendor/github.com/go-acme/lego/providers/dns/godaddy/client.go
generated
vendored
Normal file
|
@ -0,0 +1,53 @@
|
|||
package godaddy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// DNSRecord a DNS record
|
||||
type DNSRecord struct {
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Data string `json:"data"`
|
||||
Priority int `json:"priority,omitempty"`
|
||||
TTL int `json:"ttl,omitempty"`
|
||||
}
|
||||
|
||||
func (d *DNSProvider) updateRecords(records []DNSRecord, domainZone string, recordName string) error {
|
||||
body, err := json.Marshal(records)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var resp *http.Response
|
||||
resp, err = d.makeRequest(http.MethodPut, fmt.Sprintf("/v1/domains/%s/records/TXT/%s", domainZone, recordName), bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
bodyBytes, _ := ioutil.ReadAll(resp.Body)
|
||||
return fmt.Errorf("could not create record %v; Status: %v; Body: %s", string(body), resp.StatusCode, string(bodyBytes))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) makeRequest(method, uri string, body io.Reader) (*http.Response, error) {
|
||||
req, err := http.NewRequest(method, fmt.Sprintf("%s%s", defaultBaseURL, uri), body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("Accept", "application/json")
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", fmt.Sprintf("sso-key %s:%s", d.config.APIKey, d.config.APISecret))
|
||||
|
||||
return d.config.HTTPClient.Do(req)
|
||||
}
|
151
vendor/github.com/go-acme/lego/providers/dns/godaddy/godaddy.go
generated
vendored
Normal file
151
vendor/github.com/go-acme/lego/providers/dns/godaddy/godaddy.go
generated
vendored
Normal file
|
@ -0,0 +1,151 @@
|
|||
// Package godaddy implements a DNS provider for solving the DNS-01 challenge using godaddy DNS.
|
||||
package godaddy
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
)
|
||||
|
||||
const (
|
||||
// defaultBaseURL represents the API endpoint to call.
|
||||
defaultBaseURL = "https://api.godaddy.com"
|
||||
minTTL = 600
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
APIKey string
|
||||
APISecret string
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
SequenceInterval time.Duration
|
||||
TTL int
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt("GODADDY_TTL", minTTL),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("GODADDY_PROPAGATION_TIMEOUT", 120*time.Second),
|
||||
PollingInterval: env.GetOrDefaultSecond("GODADDY_POLLING_INTERVAL", 2*time.Second),
|
||||
SequenceInterval: env.GetOrDefaultSecond("GODADDY_SEQUENCE_INTERVAL", dns01.DefaultPropagationTimeout),
|
||||
HTTPClient: &http.Client{
|
||||
Timeout: env.GetOrDefaultSecond("GODADDY_HTTP_TIMEOUT", 30*time.Second),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for godaddy.
|
||||
// Credentials must be passed in the environment variables:
|
||||
// GODADDY_API_KEY and GODADDY_API_SECRET.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("GODADDY_API_KEY", "GODADDY_API_SECRET")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("godaddy: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.APIKey = values["GODADDY_API_KEY"]
|
||||
config.APISecret = values["GODADDY_API_SECRET"]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for godaddy.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("godaddy: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if config.APIKey == "" || config.APISecret == "" {
|
||||
return nil, fmt.Errorf("godaddy: credentials missing")
|
||||
}
|
||||
|
||||
if config.TTL < minTTL {
|
||||
return nil, fmt.Errorf("godaddy: invalid TTL, TTL (%d) must be greater than %d", config.TTL, minTTL)
|
||||
}
|
||||
|
||||
return &DNSProvider{config: config}, nil
|
||||
}
|
||||
|
||||
// Timeout returns the timeout and interval to use when checking for DNS
|
||||
// propagation. Adjusting here to cope with spikes in propagation times.
|
||||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
domainZone, err := d.getZone(fqdn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
recordName := d.extractRecordName(fqdn, domainZone)
|
||||
rec := []DNSRecord{
|
||||
{
|
||||
Type: "TXT",
|
||||
Name: recordName,
|
||||
Data: value,
|
||||
TTL: d.config.TTL,
|
||||
},
|
||||
}
|
||||
|
||||
return d.updateRecords(rec, domainZone, recordName)
|
||||
}
|
||||
|
||||
// CleanUp sets null value in the TXT DNS record as GoDaddy has no proper DELETE record method
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
domainZone, err := d.getZone(fqdn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
recordName := d.extractRecordName(fqdn, domainZone)
|
||||
rec := []DNSRecord{
|
||||
{
|
||||
Type: "TXT",
|
||||
Name: recordName,
|
||||
Data: "null",
|
||||
},
|
||||
}
|
||||
|
||||
return d.updateRecords(rec, domainZone, recordName)
|
||||
}
|
||||
|
||||
// Sequential All DNS challenges for this provider will be resolved sequentially.
|
||||
// Returns the interval between each iteration.
|
||||
func (d *DNSProvider) Sequential() time.Duration {
|
||||
return d.config.SequenceInterval
|
||||
}
|
||||
|
||||
func (d *DNSProvider) extractRecordName(fqdn, domain string) string {
|
||||
name := dns01.UnFqdn(fqdn)
|
||||
if idx := strings.Index(name, "."+domain); idx != -1 {
|
||||
return name[:idx]
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
func (d *DNSProvider) getZone(fqdn string) (string, error) {
|
||||
authZone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return dns01.UnFqdn(authZone), nil
|
||||
}
|
127
vendor/github.com/go-acme/lego/providers/dns/hostingde/client.go
generated
vendored
Normal file
127
vendor/github.com/go-acme/lego/providers/dns/hostingde/client.go
generated
vendored
Normal file
|
@ -0,0 +1,127 @@
|
|||
package hostingde
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/cenkalti/backoff"
|
||||
)
|
||||
|
||||
const defaultBaseURL = "https://secure.hosting.de/api/dns/v1/json"
|
||||
|
||||
// https://www.hosting.de/api/?json#list-zoneconfigs
|
||||
func (d *DNSProvider) listZoneConfigs(findRequest ZoneConfigsFindRequest) (*ZoneConfigsFindResponse, error) {
|
||||
uri := defaultBaseURL + "/zoneConfigsFind"
|
||||
|
||||
findResponse := &ZoneConfigsFindResponse{}
|
||||
|
||||
rawResp, err := d.post(uri, findRequest, findResponse)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(findResponse.Response.Data) == 0 {
|
||||
return nil, fmt.Errorf("%v: %s", err, toUnreadableBodyMessage(uri, rawResp))
|
||||
}
|
||||
|
||||
if findResponse.Status != "success" && findResponse.Status != "pending" {
|
||||
return findResponse, errors.New(toUnreadableBodyMessage(uri, rawResp))
|
||||
}
|
||||
|
||||
return findResponse, nil
|
||||
}
|
||||
|
||||
// https://www.hosting.de/api/?json#updating-zones
|
||||
func (d *DNSProvider) updateZone(updateRequest ZoneUpdateRequest) (*ZoneUpdateResponse, error) {
|
||||
uri := defaultBaseURL + "/zoneUpdate"
|
||||
|
||||
// but we'll need the ID later to delete the record
|
||||
updateResponse := &ZoneUpdateResponse{}
|
||||
|
||||
rawResp, err := d.post(uri, updateRequest, updateResponse)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if updateResponse.Status != "success" && updateResponse.Status != "pending" {
|
||||
return nil, errors.New(toUnreadableBodyMessage(uri, rawResp))
|
||||
}
|
||||
|
||||
return updateResponse, nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) getZone(findRequest ZoneConfigsFindRequest) (*ZoneConfig, error) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
var zoneConfig *ZoneConfig
|
||||
|
||||
operation := func() error {
|
||||
findResponse, err := d.listZoneConfigs(findRequest)
|
||||
if err != nil {
|
||||
cancel()
|
||||
return err
|
||||
}
|
||||
|
||||
if findResponse.Response.Data[0].Status != "active" {
|
||||
return fmt.Errorf("unexpected status: %q", findResponse.Response.Data[0].Status)
|
||||
}
|
||||
|
||||
zoneConfig = &findResponse.Response.Data[0]
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
bo := backoff.NewExponentialBackOff()
|
||||
bo.InitialInterval = 3 * time.Second
|
||||
bo.MaxInterval = 10 * bo.InitialInterval
|
||||
bo.MaxElapsedTime = 100 * bo.InitialInterval
|
||||
|
||||
// retry in case the zone was edited recently and is not yet active
|
||||
err := backoff.Retry(operation, backoff.WithContext(bo, ctx))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return zoneConfig, nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) post(uri string, request interface{}, response interface{}) ([]byte, error) {
|
||||
body, err := json.Marshal(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, uri, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := d.config.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error querying API: %v", err)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
content, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, errors.New(toUnreadableBodyMessage(uri, content))
|
||||
}
|
||||
|
||||
err = json.Unmarshal(content, response)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%v: %s", err, toUnreadableBodyMessage(uri, content))
|
||||
}
|
||||
|
||||
return content, nil
|
||||
}
|
||||
|
||||
func toUnreadableBodyMessage(uri string, rawBody []byte) string {
|
||||
return fmt.Sprintf("the request %s sent a response with a body which is an invalid format: %q", uri, string(rawBody))
|
||||
}
|
183
vendor/github.com/go-acme/lego/providers/dns/hostingde/hostingde.go
generated
vendored
Normal file
183
vendor/github.com/go-acme/lego/providers/dns/hostingde/hostingde.go
generated
vendored
Normal file
|
@ -0,0 +1,183 @@
|
|||
// Package hostingde implements a DNS provider for solving the DNS-01 challenge using hosting.de.
|
||||
package hostingde
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
APIKey string
|
||||
ZoneName string
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt("HOSTINGDE_TTL", dns01.DefaultTTL),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("HOSTINGDE_PROPAGATION_TIMEOUT", 2*time.Minute),
|
||||
PollingInterval: env.GetOrDefaultSecond("HOSTINGDE_POLLING_INTERVAL", 2*time.Second),
|
||||
HTTPClient: &http.Client{
|
||||
Timeout: env.GetOrDefaultSecond("HOSTINGDE_HTTP_TIMEOUT", 30*time.Second),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
recordIDs map[string]string
|
||||
recordIDsMu sync.Mutex
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for hosting.de.
|
||||
// Credentials must be passed in the environment variables:
|
||||
// HOSTINGDE_ZONE_NAME and HOSTINGDE_API_KEY
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("HOSTINGDE_API_KEY", "HOSTINGDE_ZONE_NAME")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("hostingde: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.APIKey = values["HOSTINGDE_API_KEY"]
|
||||
config.ZoneName = values["HOSTINGDE_ZONE_NAME"]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for hosting.de.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("hostingde: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if config.APIKey == "" {
|
||||
return nil, errors.New("hostingde: API key missing")
|
||||
}
|
||||
|
||||
if config.ZoneName == "" {
|
||||
return nil, errors.New("hostingde: Zone Name missing")
|
||||
}
|
||||
|
||||
return &DNSProvider{
|
||||
config: config,
|
||||
recordIDs: make(map[string]string),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Timeout returns the timeout and interval to use when checking for DNS propagation.
|
||||
// Adjusting here to cope with spikes in propagation times.
|
||||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
// get the ZoneConfig for that domain
|
||||
zonesFind := ZoneConfigsFindRequest{
|
||||
Filter: Filter{
|
||||
Field: "zoneName",
|
||||
Value: domain,
|
||||
},
|
||||
Limit: 1,
|
||||
Page: 1,
|
||||
}
|
||||
zonesFind.AuthToken = d.config.APIKey
|
||||
|
||||
zoneConfig, err := d.getZone(zonesFind)
|
||||
if err != nil {
|
||||
return fmt.Errorf("hostingde: %v", err)
|
||||
}
|
||||
zoneConfig.Name = d.config.ZoneName
|
||||
|
||||
rec := []DNSRecord{{
|
||||
Type: "TXT",
|
||||
Name: dns01.UnFqdn(fqdn),
|
||||
Content: value,
|
||||
TTL: d.config.TTL,
|
||||
}}
|
||||
|
||||
req := ZoneUpdateRequest{
|
||||
ZoneConfig: *zoneConfig,
|
||||
RecordsToAdd: rec,
|
||||
}
|
||||
req.AuthToken = d.config.APIKey
|
||||
|
||||
resp, err := d.updateZone(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("hostingde: %v", err)
|
||||
}
|
||||
|
||||
for _, record := range resp.Response.Records {
|
||||
if record.Name == dns01.UnFqdn(fqdn) && record.Content == fmt.Sprintf(`"%s"`, value) {
|
||||
d.recordIDsMu.Lock()
|
||||
d.recordIDs[fqdn] = record.ID
|
||||
d.recordIDsMu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
if d.recordIDs[fqdn] == "" {
|
||||
return fmt.Errorf("hostingde: error getting ID of just created record, for domain %s", domain)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
rec := []DNSRecord{{
|
||||
Type: "TXT",
|
||||
Name: dns01.UnFqdn(fqdn),
|
||||
Content: `"` + value + `"`,
|
||||
}}
|
||||
|
||||
// get the ZoneConfig for that domain
|
||||
zonesFind := ZoneConfigsFindRequest{
|
||||
Filter: Filter{
|
||||
Field: "zoneName",
|
||||
Value: domain,
|
||||
},
|
||||
Limit: 1,
|
||||
Page: 1,
|
||||
}
|
||||
zonesFind.AuthToken = d.config.APIKey
|
||||
|
||||
zoneConfig, err := d.getZone(zonesFind)
|
||||
if err != nil {
|
||||
return fmt.Errorf("hostingde: %v", err)
|
||||
}
|
||||
zoneConfig.Name = d.config.ZoneName
|
||||
|
||||
req := ZoneUpdateRequest{
|
||||
ZoneConfig: *zoneConfig,
|
||||
RecordsToDelete: rec,
|
||||
}
|
||||
req.AuthToken = d.config.APIKey
|
||||
|
||||
// Delete record ID from map
|
||||
d.recordIDsMu.Lock()
|
||||
delete(d.recordIDs, fqdn)
|
||||
d.recordIDsMu.Unlock()
|
||||
|
||||
_, err = d.updateZone(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("hostingde: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
139
vendor/github.com/go-acme/lego/providers/dns/hostingde/model.go
generated
vendored
Normal file
139
vendor/github.com/go-acme/lego/providers/dns/hostingde/model.go
generated
vendored
Normal file
|
@ -0,0 +1,139 @@
|
|||
package hostingde
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
// APIError represents an error in an API response.
|
||||
// https://www.hosting.de/api/?json#warnings-and-errors
|
||||
type APIError struct {
|
||||
Code int `json:"code"`
|
||||
ContextObject string `json:"contextObject"`
|
||||
ContextPath string `json:"contextPath"`
|
||||
Details []string `json:"details"`
|
||||
Text string `json:"text"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
// Filter is used to filter FindRequests to the API.
|
||||
// https://www.hosting.de/api/?json#filter-object
|
||||
type Filter struct {
|
||||
Field string `json:"field"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
// Sort is used to sort FindRequests from the API.
|
||||
// https://www.hosting.de/api/?json#filtering-and-sorting
|
||||
type Sort struct {
|
||||
Field string `json:"zoneName"`
|
||||
Order string `json:"order"`
|
||||
}
|
||||
|
||||
// Metadata represents the metadata in an API response.
|
||||
// https://www.hosting.de/api/?json#metadata-object
|
||||
type Metadata struct {
|
||||
ClientTransactionID string `json:"clientTransactionId"`
|
||||
ServerTransactionID string `json:"serverTransactionId"`
|
||||
}
|
||||
|
||||
// ZoneConfig The ZoneConfig object defines a zone.
|
||||
// https://www.hosting.de/api/?json#the-zoneconfig-object
|
||||
type ZoneConfig struct {
|
||||
ID string `json:"id"`
|
||||
AccountID string `json:"accountId"`
|
||||
Status string `json:"status"`
|
||||
Name string `json:"name"`
|
||||
NameUnicode string `json:"nameUnicode"`
|
||||
MasterIP string `json:"masterIp"`
|
||||
Type string `json:"type"`
|
||||
EMailAddress string `json:"emailAddress"`
|
||||
ZoneTransferWhitelist []string `json:"zoneTransferWhitelist"`
|
||||
LastChangeDate string `json:"lastChangeDate"`
|
||||
DNSServerGroupID string `json:"dnsServerGroupId"`
|
||||
DNSSecMode string `json:"dnsSecMode"`
|
||||
SOAValues *SOAValues `json:"soaValues,omitempty"`
|
||||
TemplateValues json.RawMessage `json:"templateValues,omitempty"`
|
||||
}
|
||||
|
||||
// SOAValues The SOA values object contains the time (seconds) used in a zone’s SOA record.
|
||||
// https://www.hosting.de/api/?json#the-soa-values-object
|
||||
type SOAValues struct {
|
||||
Refresh int `json:"refresh"`
|
||||
Retry int `json:"retry"`
|
||||
Expire int `json:"expire"`
|
||||
TTL int `json:"ttl"`
|
||||
NegativeTTL int `json:"negativeTtl"`
|
||||
}
|
||||
|
||||
// DNSRecord The DNS Record object is part of a zone. It is used to manage DNS resource records.
|
||||
// https://www.hosting.de/api/?json#the-record-object
|
||||
type DNSRecord struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
ZoneID string `json:"zoneId,omitempty"`
|
||||
RecordTemplateID string `json:"recordTemplateId,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
Content string `json:"content,omitempty"`
|
||||
TTL int `json:"ttl,omitempty"`
|
||||
Priority int `json:"priority,omitempty"`
|
||||
LastChangeDate string `json:"lastChangeDate,omitempty"`
|
||||
}
|
||||
|
||||
// Zone The Zone Object.
|
||||
// https://www.hosting.de/api/?json#the-zone-object
|
||||
type Zone struct {
|
||||
Records []DNSRecord `json:"records"`
|
||||
ZoneConfig ZoneConfig `json:"zoneConfig"`
|
||||
}
|
||||
|
||||
// ZoneUpdateRequest represents a API ZoneUpdate request.
|
||||
// https://www.hosting.de/api/?json#updating-zones
|
||||
type ZoneUpdateRequest struct {
|
||||
BaseRequest
|
||||
ZoneConfig `json:"zoneConfig"`
|
||||
RecordsToAdd []DNSRecord `json:"recordsToAdd"`
|
||||
RecordsToDelete []DNSRecord `json:"recordsToDelete"`
|
||||
}
|
||||
|
||||
// ZoneUpdateResponse represents a response from the API.
|
||||
// https://www.hosting.de/api/?json#updating-zones
|
||||
type ZoneUpdateResponse struct {
|
||||
BaseResponse
|
||||
Response Zone `json:"response"`
|
||||
}
|
||||
|
||||
// ZoneConfigsFindRequest represents a API ZonesFind request.
|
||||
// https://www.hosting.de/api/?json#list-zoneconfigs
|
||||
type ZoneConfigsFindRequest struct {
|
||||
BaseRequest
|
||||
Filter Filter `json:"filter"`
|
||||
Limit int `json:"limit"`
|
||||
Page int `json:"page"`
|
||||
Sort *Sort `json:"sort,omitempty"`
|
||||
}
|
||||
|
||||
// ZoneConfigsFindResponse represents the API response for ZoneConfigsFind.
|
||||
// https://www.hosting.de/api/?json#list-zoneconfigs
|
||||
type ZoneConfigsFindResponse struct {
|
||||
BaseResponse
|
||||
Response struct {
|
||||
Limit int `json:"limit"`
|
||||
Page int `json:"page"`
|
||||
TotalEntries int `json:"totalEntries"`
|
||||
TotalPages int `json:"totalPages"`
|
||||
Type string `json:"type"`
|
||||
Data []ZoneConfig `json:"data"`
|
||||
} `json:"response"`
|
||||
}
|
||||
|
||||
// BaseResponse Common response struct.
|
||||
// https://www.hosting.de/api/?json#responses
|
||||
type BaseResponse struct {
|
||||
Errors []APIError `json:"errors"`
|
||||
Metadata Metadata `json:"metadata"`
|
||||
Warnings []string `json:"warnings"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
// BaseRequest Common request struct.
|
||||
type BaseRequest struct {
|
||||
AuthToken string `json:"authToken"`
|
||||
}
|
195
vendor/github.com/go-acme/lego/providers/dns/httpreq/httpreq.go
generated
vendored
Normal file
195
vendor/github.com/go-acme/lego/providers/dns/httpreq/httpreq.go
generated
vendored
Normal file
|
@ -0,0 +1,195 @@
|
|||
// Package httpreq implements a DNS provider for solving the DNS-01 challenge through a HTTP server.
|
||||
package httpreq
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
)
|
||||
|
||||
type message struct {
|
||||
FQDN string `json:"fqdn"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type messageRaw struct {
|
||||
Domain string `json:"domain"`
|
||||
Token string `json:"token"`
|
||||
KeyAuth string `json:"keyAuth"`
|
||||
}
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
Endpoint *url.URL
|
||||
Mode string
|
||||
Username string
|
||||
Password string
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
PropagationTimeout: env.GetOrDefaultSecond("HTTPREQ_PROPAGATION_TIMEOUT", dns01.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("HTTPREQ_POLLING_INTERVAL", dns01.DefaultPollingInterval),
|
||||
HTTPClient: &http.Client{
|
||||
Timeout: env.GetOrDefaultSecond("HTTPREQ_HTTP_TIMEOUT", 30*time.Second),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider describes a provider for acme-proxy
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("HTTPREQ_ENDPOINT")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("httpreq: %v", err)
|
||||
}
|
||||
|
||||
endpoint, err := url.Parse(values["HTTPREQ_ENDPOINT"])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("httpreq: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.Mode = os.Getenv("HTTPREQ_MODE")
|
||||
config.Username = os.Getenv("HTTPREQ_USERNAME")
|
||||
config.Password = os.Getenv("HTTPREQ_PASSWORD")
|
||||
config.Endpoint = endpoint
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider .
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("httpreq: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if config.Endpoint == nil {
|
||||
return nil, errors.New("httpreq: the endpoint is missing")
|
||||
}
|
||||
|
||||
return &DNSProvider{config: config}, nil
|
||||
}
|
||||
|
||||
// Timeout returns the timeout and interval to use when checking for DNS propagation.
|
||||
// Adjusting here to cope with spikes in propagation times.
|
||||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
if d.config.Mode == "RAW" {
|
||||
msg := &messageRaw{
|
||||
Domain: domain,
|
||||
Token: token,
|
||||
KeyAuth: keyAuth,
|
||||
}
|
||||
|
||||
err := d.doPost("/present", msg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("httpreq: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
msg := &message{
|
||||
FQDN: fqdn,
|
||||
Value: value,
|
||||
}
|
||||
|
||||
err := d.doPost("/present", msg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("httpreq: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
if d.config.Mode == "RAW" {
|
||||
msg := &messageRaw{
|
||||
Domain: domain,
|
||||
Token: token,
|
||||
KeyAuth: keyAuth,
|
||||
}
|
||||
|
||||
err := d.doPost("/cleanup", msg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("httpreq: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
msg := &message{
|
||||
FQDN: fqdn,
|
||||
Value: value,
|
||||
}
|
||||
|
||||
err := d.doPost("/cleanup", msg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("httpreq: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) doPost(uri string, msg interface{}) error {
|
||||
reqBody := &bytes.Buffer{}
|
||||
err := json.NewEncoder(reqBody).Encode(msg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
newURI := path.Join(d.config.Endpoint.EscapedPath(), uri)
|
||||
endpoint, err := d.config.Endpoint.Parse(newURI)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, endpoint.String(), reqBody)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
if len(d.config.Username) > 0 && len(d.config.Password) > 0 {
|
||||
req.SetBasicAuth(d.config.Username, d.config.Password)
|
||||
}
|
||||
|
||||
resp, err := d.config.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode >= http.StatusBadRequest {
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%d: failed to read response body: %v", resp.StatusCode, err)
|
||||
}
|
||||
|
||||
return fmt.Errorf("%d: request failed: %v", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
239
vendor/github.com/go-acme/lego/providers/dns/iij/iij.go
generated
vendored
Normal file
239
vendor/github.com/go-acme/lego/providers/dns/iij/iij.go
generated
vendored
Normal file
|
@ -0,0 +1,239 @@
|
|||
// Package iij implements a DNS provider for solving the DNS-01 challenge using IIJ DNS.
|
||||
package iij
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
"github.com/iij/doapi"
|
||||
"github.com/iij/doapi/protocol"
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
AccessKey string
|
||||
SecretKey string
|
||||
DoServiceCode string
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt("IIJ_TTL", 300),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("IIJ_PROPAGATION_TIMEOUT", 2*time.Minute),
|
||||
PollingInterval: env.GetOrDefaultSecond("IIJ_POLLING_INTERVAL", 4*time.Second),
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider implements the acme.ChallengeProvider interface
|
||||
type DNSProvider struct {
|
||||
api *doapi.API
|
||||
config *Config
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for IIJ DO
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("IIJ_API_ACCESS_KEY", "IIJ_API_SECRET_KEY", "IIJ_DO_SERVICE_CODE")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("iij: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.AccessKey = values["IIJ_API_ACCESS_KEY"]
|
||||
config.SecretKey = values["IIJ_API_SECRET_KEY"]
|
||||
config.DoServiceCode = values["IIJ_DO_SERVICE_CODE"]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig takes a given config
|
||||
// and returns a custom configured DNSProvider instance
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config.SecretKey == "" || config.AccessKey == "" || config.DoServiceCode == "" {
|
||||
return nil, fmt.Errorf("iij: credentials missing")
|
||||
}
|
||||
|
||||
return &DNSProvider{
|
||||
api: doapi.NewAPI(config.AccessKey, config.SecretKey),
|
||||
config: config,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Timeout returns the timeout and interval to use when checking for DNS propagation.
|
||||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
// Present creates a TXT record using the specified parameters
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
_, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
err := d.addTxtRecord(domain, value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("iij: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
_, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
err := d.deleteTxtRecord(domain, value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("iij: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) addTxtRecord(domain, value string) error {
|
||||
zones, err := d.listZones()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
owner, zone, err := splitDomain(domain, zones)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
request := protocol.RecordAdd{
|
||||
DoServiceCode: d.config.DoServiceCode,
|
||||
ZoneName: zone,
|
||||
Owner: owner,
|
||||
TTL: strconv.Itoa(d.config.TTL),
|
||||
RecordType: "TXT",
|
||||
RData: value,
|
||||
}
|
||||
|
||||
response := &protocol.RecordAddResponse{}
|
||||
|
||||
if err := doapi.Call(*d.api, request, response); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return d.commit()
|
||||
}
|
||||
|
||||
func (d *DNSProvider) deleteTxtRecord(domain, value string) error {
|
||||
zones, err := d.listZones()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
owner, zone, err := splitDomain(domain, zones)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
id, err := d.findTxtRecord(owner, zone, value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
request := protocol.RecordDelete{
|
||||
DoServiceCode: d.config.DoServiceCode,
|
||||
ZoneName: zone,
|
||||
RecordID: id,
|
||||
}
|
||||
|
||||
response := &protocol.RecordDeleteResponse{}
|
||||
|
||||
if err := doapi.Call(*d.api, request, response); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return d.commit()
|
||||
}
|
||||
|
||||
func (d *DNSProvider) commit() error {
|
||||
request := protocol.Commit{
|
||||
DoServiceCode: d.config.DoServiceCode,
|
||||
}
|
||||
|
||||
response := &protocol.CommitResponse{}
|
||||
|
||||
return doapi.Call(*d.api, request, response)
|
||||
}
|
||||
|
||||
func (d *DNSProvider) findTxtRecord(owner, zone, value string) (string, error) {
|
||||
request := protocol.RecordListGet{
|
||||
DoServiceCode: d.config.DoServiceCode,
|
||||
ZoneName: zone,
|
||||
}
|
||||
|
||||
response := &protocol.RecordListGetResponse{}
|
||||
|
||||
if err := doapi.Call(*d.api, request, response); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var id string
|
||||
|
||||
for _, record := range response.RecordList {
|
||||
if record.Owner == owner && record.RecordType == "TXT" && record.RData == "\""+value+"\"" {
|
||||
id = record.Id
|
||||
}
|
||||
}
|
||||
|
||||
if id == "" {
|
||||
return "", fmt.Errorf("%s record in %s not found", owner, zone)
|
||||
}
|
||||
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) listZones() ([]string, error) {
|
||||
request := protocol.ZoneListGet{
|
||||
DoServiceCode: d.config.DoServiceCode,
|
||||
}
|
||||
|
||||
response := &protocol.ZoneListGetResponse{}
|
||||
|
||||
if err := doapi.Call(*d.api, request, response); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return response.ZoneList, nil
|
||||
}
|
||||
|
||||
func splitDomain(domain string, zones []string) (string, string, error) {
|
||||
parts := strings.Split(strings.Trim(domain, "."), ".")
|
||||
|
||||
var owner string
|
||||
var zone string
|
||||
|
||||
for i := 0; i < len(parts)-1; i++ {
|
||||
zone = strings.Join(parts[i:], ".")
|
||||
if zoneContains(zone, zones) {
|
||||
baseOwner := strings.Join(parts[0:i], ".")
|
||||
if len(baseOwner) > 0 {
|
||||
baseOwner = "." + baseOwner
|
||||
}
|
||||
owner = "_acme-challenge" + baseOwner
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if len(owner) == 0 {
|
||||
return "", "", fmt.Errorf("%s not found", domain)
|
||||
}
|
||||
|
||||
return owner, zone, nil
|
||||
}
|
||||
|
||||
func zoneContains(zone string, zones []string) bool {
|
||||
for _, z := range zones {
|
||||
if zone == z {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
166
vendor/github.com/go-acme/lego/providers/dns/inwx/inwx.go
generated
vendored
Normal file
166
vendor/github.com/go-acme/lego/providers/dns/inwx/inwx.go
generated
vendored
Normal file
|
@ -0,0 +1,166 @@
|
|||
// Package inwx implements a DNS provider for solving the DNS-01 challenge using inwx dom robot
|
||||
package inwx
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/log"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
"github.com/nrdcg/goinwx"
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
Username string
|
||||
Password string
|
||||
Sandbox bool
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
PropagationTimeout: env.GetOrDefaultSecond("INWX_PROPAGATION_TIMEOUT", dns01.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("INWX_POLLING_INTERVAL", dns01.DefaultPollingInterval),
|
||||
TTL: env.GetOrDefaultInt("INWX_TTL", 300),
|
||||
Sandbox: env.GetOrDefaultBool("INWX_SANDBOX", false),
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
client *goinwx.Client
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for Dyn DNS.
|
||||
// Credentials must be passed in the environment variables:
|
||||
// INWX_USERNAME and INWX_PASSWORD.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("INWX_USERNAME", "INWX_PASSWORD")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("inwx: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.Username = values["INWX_USERNAME"]
|
||||
config.Password = values["INWX_PASSWORD"]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for Dyn DNS
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("inwx: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if config.Username == "" || config.Password == "" {
|
||||
return nil, fmt.Errorf("inwx: credentials missing")
|
||||
}
|
||||
|
||||
if config.Sandbox {
|
||||
log.Infof("inwx: sandbox mode is enabled")
|
||||
}
|
||||
|
||||
client := goinwx.NewClient(config.Username, config.Password, &goinwx.ClientOptions{Sandbox: config.Sandbox})
|
||||
|
||||
return &DNSProvider{config: config, client: client}, nil
|
||||
}
|
||||
|
||||
// Present creates a TXT record using the specified parameters
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
authZone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("inwx: %v", err)
|
||||
}
|
||||
|
||||
err = d.client.Account.Login()
|
||||
if err != nil {
|
||||
return fmt.Errorf("inwx: %v", err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
errL := d.client.Account.Logout()
|
||||
if errL != nil {
|
||||
log.Infof("inwx: failed to logout: %v", errL)
|
||||
}
|
||||
}()
|
||||
|
||||
var request = &goinwx.NameserverRecordRequest{
|
||||
Domain: dns01.UnFqdn(authZone),
|
||||
Name: dns01.UnFqdn(fqdn),
|
||||
Type: "TXT",
|
||||
Content: value,
|
||||
TTL: d.config.TTL,
|
||||
}
|
||||
|
||||
_, err = d.client.Nameservers.CreateRecord(request)
|
||||
if err != nil {
|
||||
switch er := err.(type) {
|
||||
case *goinwx.ErrorResponse:
|
||||
if er.Message == "Object exists" {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("inwx: %v", err)
|
||||
default:
|
||||
return fmt.Errorf("inwx: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
authZone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("inwx: %v", err)
|
||||
}
|
||||
|
||||
err = d.client.Account.Login()
|
||||
if err != nil {
|
||||
return fmt.Errorf("inwx: %v", err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
errL := d.client.Account.Logout()
|
||||
if errL != nil {
|
||||
log.Infof("inwx: failed to logout: %v", errL)
|
||||
}
|
||||
}()
|
||||
|
||||
response, err := d.client.Nameservers.Info(&goinwx.NameserverInfoRequest{
|
||||
Domain: dns01.UnFqdn(authZone),
|
||||
Name: dns01.UnFqdn(fqdn),
|
||||
Type: "TXT",
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("inwx: %v", err)
|
||||
}
|
||||
|
||||
var lastErr error
|
||||
for _, record := range response.Records {
|
||||
err = d.client.Nameservers.DeleteRecord(record.ID)
|
||||
if err != nil {
|
||||
lastErr = fmt.Errorf("inwx: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return lastErr
|
||||
}
|
||||
|
||||
// Timeout returns the timeout and interval to use when checking for DNS propagation.
|
||||
// Adjusting here to cope with spikes in propagation times.
|
||||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
153
vendor/github.com/go-acme/lego/providers/dns/lightsail/lightsail.go
generated
vendored
Normal file
153
vendor/github.com/go-acme/lego/providers/dns/lightsail/lightsail.go
generated
vendored
Normal file
|
@ -0,0 +1,153 @@
|
|||
// Package lightsail implements a DNS provider for solving the DNS-01 challenge using AWS Lightsail DNS.
|
||||
package lightsail
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/client"
|
||||
"github.com/aws/aws-sdk-go/aws/request"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/lightsail"
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
)
|
||||
|
||||
const (
|
||||
maxRetries = 5
|
||||
)
|
||||
|
||||
// customRetryer implements the client.Retryer interface by composing the DefaultRetryer.
|
||||
// It controls the logic for retrying recoverable request errors (e.g. when rate limits are exceeded).
|
||||
type customRetryer struct {
|
||||
client.DefaultRetryer
|
||||
}
|
||||
|
||||
// RetryRules overwrites the DefaultRetryer's method.
|
||||
// It uses a basic exponential backoff algorithm that returns an initial
|
||||
// delay of ~400ms with an upper limit of ~30 seconds which should prevent
|
||||
// causing a high number of consecutive throttling errors.
|
||||
// For reference: Route 53 enforces an account-wide(!) 5req/s query limit.
|
||||
func (c customRetryer) RetryRules(r *request.Request) time.Duration {
|
||||
retryCount := r.RetryCount
|
||||
if retryCount > 7 {
|
||||
retryCount = 7
|
||||
}
|
||||
|
||||
delay := (1 << uint(retryCount)) * (rand.Intn(50) + 200)
|
||||
return time.Duration(delay) * time.Millisecond
|
||||
}
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
DNSZone string
|
||||
Region string
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
DNSZone: env.GetOrFile("DNS_ZONE"),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("LIGHTSAIL_PROPAGATION_TIMEOUT", dns01.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("LIGHTSAIL_POLLING_INTERVAL", dns01.DefaultPollingInterval),
|
||||
Region: env.GetOrDefaultString("LIGHTSAIL_REGION", "us-east-1"),
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider implements the acme.ChallengeProvider interface
|
||||
type DNSProvider struct {
|
||||
client *lightsail.Lightsail
|
||||
config *Config
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for the AWS Lightsail service.
|
||||
//
|
||||
// AWS Credentials are automatically detected in the following locations
|
||||
// and prioritized in the following order:
|
||||
// 1. Environment variables: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY,
|
||||
// [AWS_SESSION_TOKEN], [DNS_ZONE], [LIGHTSAIL_REGION]
|
||||
// 2. Shared credentials file (defaults to ~/.aws/credentials)
|
||||
// 3. Amazon EC2 IAM role
|
||||
//
|
||||
// public hosted zone via the FQDN.
|
||||
//
|
||||
// See also: https://github.com/aws/aws-sdk-go/wiki/configuring-sdk
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
return NewDNSProviderConfig(NewDefaultConfig())
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for AWS Lightsail.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("lightsail: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
retryer := customRetryer{}
|
||||
retryer.NumMaxRetries = maxRetries
|
||||
|
||||
conf := aws.NewConfig().WithRegion(config.Region)
|
||||
sess, err := session.NewSession(request.WithRetryer(conf, retryer))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &DNSProvider{
|
||||
config: config,
|
||||
client: lightsail.New(sess),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Present creates a TXT record using the specified parameters
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
err := d.newTxtRecord(fqdn, `"`+value+`"`)
|
||||
if err != nil {
|
||||
return fmt.Errorf("lightsail: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
params := &lightsail.DeleteDomainEntryInput{
|
||||
DomainName: aws.String(d.config.DNSZone),
|
||||
DomainEntry: &lightsail.DomainEntry{
|
||||
Name: aws.String(fqdn),
|
||||
Type: aws.String("TXT"),
|
||||
Target: aws.String(`"` + value + `"`),
|
||||
},
|
||||
}
|
||||
|
||||
_, err := d.client.DeleteDomainEntry(params)
|
||||
if err != nil {
|
||||
return fmt.Errorf("lightsail: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Timeout returns the timeout and interval to use when checking for DNS propagation.
|
||||
// Adjusting here to cope with spikes in propagation times.
|
||||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
func (d *DNSProvider) newTxtRecord(fqdn string, value string) error {
|
||||
params := &lightsail.CreateDomainEntryInput{
|
||||
DomainName: aws.String(d.config.DNSZone),
|
||||
DomainEntry: &lightsail.DomainEntry{
|
||||
Name: aws.String(fqdn),
|
||||
Target: aws.String(value),
|
||||
Type: aws.String("TXT"),
|
||||
},
|
||||
}
|
||||
_, err := d.client.CreateDomainEntry(params)
|
||||
return err
|
||||
}
|
164
vendor/github.com/go-acme/lego/providers/dns/linode/linode.go
generated
vendored
Normal file
164
vendor/github.com/go-acme/lego/providers/dns/linode/linode.go
generated
vendored
Normal file
|
@ -0,0 +1,164 @@
|
|||
// Package linode implements a DNS provider for solving the DNS-01 challenge using Linode DNS.
|
||||
package linode
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
"github.com/timewasted/linode/dns"
|
||||
)
|
||||
|
||||
const (
|
||||
minTTL = 300
|
||||
dnsUpdateFreqMins = 15
|
||||
dnsUpdateFudgeSecs = 120
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
APIKey string
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
PollingInterval: env.GetOrDefaultSecond("LINODE_POLLING_INTERVAL", 15*time.Second),
|
||||
TTL: env.GetOrDefaultInt("LINODE_TTL", minTTL),
|
||||
}
|
||||
}
|
||||
|
||||
type hostedZoneInfo struct {
|
||||
domainID int
|
||||
resourceName string
|
||||
}
|
||||
|
||||
// DNSProvider implements the acme.ChallengeProvider interface.
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
client *dns.DNS
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for Linode.
|
||||
// Credentials must be passed in the environment variable: LINODE_API_KEY.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("LINODE_API_KEY")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("linode: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.APIKey = values["LINODE_API_KEY"]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for Linode.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("linode: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if len(config.APIKey) == 0 {
|
||||
return nil, errors.New("linode: credentials missing")
|
||||
}
|
||||
|
||||
if config.TTL < minTTL {
|
||||
return nil, fmt.Errorf("linode: invalid TTL, TTL (%d) must be greater than %d", config.TTL, minTTL)
|
||||
}
|
||||
|
||||
return &DNSProvider{
|
||||
config: config,
|
||||
client: dns.New(config.APIKey),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Timeout returns the timeout and interval to use when checking for DNS
|
||||
// propagation. Adjusting here to cope with spikes in propagation times.
|
||||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
// Since Linode only updates their zone files every X minutes, we need
|
||||
// to figure out how many minutes we have to wait until we hit the next
|
||||
// interval of X. We then wait another couple of minutes, just to be
|
||||
// safe. Hopefully at some point during all of this, the record will
|
||||
// have propagated throughout Linode's network.
|
||||
minsRemaining := dnsUpdateFreqMins - (time.Now().Minute() % dnsUpdateFreqMins)
|
||||
|
||||
timeout = (time.Duration(minsRemaining) * time.Minute) +
|
||||
(minTTL * time.Second) +
|
||||
(dnsUpdateFudgeSecs * time.Second)
|
||||
interval = d.config.PollingInterval
|
||||
return
|
||||
}
|
||||
|
||||
// Present creates a TXT record using the specified parameters.
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
zone, err := d.getHostedZoneInfo(fqdn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = d.client.CreateDomainResourceTXT(zone.domainID, dns01.UnFqdn(fqdn), value, d.config.TTL); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters.
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
zone, err := d.getHostedZoneInfo(fqdn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get all TXT records for the specified domain.
|
||||
resources, err := d.client.GetResourcesByType(zone.domainID, "TXT")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove the specified resource, if it exists.
|
||||
for _, resource := range resources {
|
||||
if resource.Name == zone.resourceName && resource.Target == value {
|
||||
resp, err := d.client.DeleteDomainResource(resource.DomainID, resource.ResourceID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.ResourceID != resource.ResourceID {
|
||||
return errors.New("error deleting resource: resource IDs do not match")
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) getHostedZoneInfo(fqdn string) (*hostedZoneInfo, error) {
|
||||
// Lookup the zone that handles the specified FQDN.
|
||||
authZone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resourceName := strings.TrimSuffix(fqdn, "."+authZone)
|
||||
|
||||
// Query the authority zone.
|
||||
domain, err := d.client.GetDomain(dns01.UnFqdn(authZone))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &hostedZoneInfo{
|
||||
domainID: domain.DomainID,
|
||||
resourceName: resourceName,
|
||||
}, nil
|
||||
}
|
190
vendor/github.com/go-acme/lego/providers/dns/linodev4/linodev4.go
generated
vendored
Normal file
190
vendor/github.com/go-acme/lego/providers/dns/linodev4/linodev4.go
generated
vendored
Normal file
|
@ -0,0 +1,190 @@
|
|||
// Package linodev4 implements a DNS provider for solving the DNS-01 challenge using Linode DNS and Linode's APIv4
|
||||
package linodev4
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
"github.com/linode/linodego"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
const (
|
||||
minTTL = 300
|
||||
dnsUpdateFreqMins = 15
|
||||
dnsUpdateFudgeSecs = 120
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
Token string
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
HTTPTimeout time.Duration
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
PollingInterval: env.GetOrDefaultSecond("LINODE_POLLING_INTERVAL", 15*time.Second),
|
||||
TTL: env.GetOrDefaultInt("LINODE_TTL", minTTL),
|
||||
HTTPTimeout: env.GetOrDefaultSecond("LINODE_HTTP_TIMEOUT", 0),
|
||||
}
|
||||
}
|
||||
|
||||
type hostedZoneInfo struct {
|
||||
domainID int
|
||||
resourceName string
|
||||
}
|
||||
|
||||
// DNSProvider implements the acme.ChallengeProvider interface.
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
client *linodego.Client
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for Linode.
|
||||
// Credentials must be passed in the environment variable: LINODE_TOKEN.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("LINODE_TOKEN")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("linodev4: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.Token = values["LINODE_TOKEN"]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for Linode.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("linodev4: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if len(config.Token) == 0 {
|
||||
return nil, errors.New("linodev4: Linode Access Token missing")
|
||||
}
|
||||
|
||||
if config.TTL < minTTL {
|
||||
return nil, fmt.Errorf("linodev4: invalid TTL, TTL (%d) must be greater than %d", config.TTL, minTTL)
|
||||
}
|
||||
|
||||
tokenSource := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: config.Token})
|
||||
oauth2Client := &http.Client{
|
||||
Timeout: config.HTTPTimeout,
|
||||
Transport: &oauth2.Transport{
|
||||
Source: tokenSource,
|
||||
},
|
||||
}
|
||||
|
||||
client := linodego.NewClient(oauth2Client)
|
||||
client.SetUserAgent(fmt.Sprintf("lego-dns linodego/%s", linodego.Version))
|
||||
|
||||
return &DNSProvider{
|
||||
config: config,
|
||||
client: &client,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Timeout returns the timeout and interval to use when checking for DNS
|
||||
// propagation. Adjusting here to cope with spikes in propagation times.
|
||||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
// Since Linode only updates their zone files every X minutes, we need
|
||||
// to figure out how many minutes we have to wait until we hit the next
|
||||
// interval of X. We then wait another couple of minutes, just to be
|
||||
// safe. Hopefully at some point during all of this, the record will
|
||||
// have propagated throughout Linode's network.
|
||||
minsRemaining := dnsUpdateFreqMins - (time.Now().Minute() % dnsUpdateFreqMins)
|
||||
|
||||
timeout = (time.Duration(minsRemaining) * time.Minute) +
|
||||
(minTTL * time.Second) +
|
||||
(dnsUpdateFudgeSecs * time.Second)
|
||||
interval = d.config.PollingInterval
|
||||
return
|
||||
}
|
||||
|
||||
// Present creates a TXT record using the specified parameters.
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
zone, err := d.getHostedZoneInfo(fqdn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
createOpts := linodego.DomainRecordCreateOptions{
|
||||
Name: dns01.UnFqdn(fqdn),
|
||||
Target: value,
|
||||
TTLSec: d.config.TTL,
|
||||
Type: linodego.RecordTypeTXT,
|
||||
}
|
||||
|
||||
_, err = d.client.CreateDomainRecord(context.Background(), zone.domainID, createOpts)
|
||||
return err
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters.
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
zone, err := d.getHostedZoneInfo(fqdn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get all TXT records for the specified domain.
|
||||
listOpts := linodego.NewListOptions(0, "{\"type\":\"TXT\"}")
|
||||
resources, err := d.client.ListDomainRecords(context.Background(), zone.domainID, listOpts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove the specified resource, if it exists.
|
||||
for _, resource := range resources {
|
||||
if (resource.Name == strings.TrimSuffix(fqdn, ".") || resource.Name == zone.resourceName) &&
|
||||
resource.Target == value {
|
||||
if err := d.client.DeleteDomainRecord(context.Background(), zone.domainID, resource.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) getHostedZoneInfo(fqdn string) (*hostedZoneInfo, error) {
|
||||
// Lookup the zone that handles the specified FQDN.
|
||||
authZone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Query the authority zone.
|
||||
data, err := json.Marshal(map[string]string{"domain": dns01.UnFqdn(authZone)})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
listOpts := linodego.NewListOptions(0, string(data))
|
||||
domains, err := d.client.ListDomains(context.Background(), listOpts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(domains) == 0 {
|
||||
return nil, fmt.Errorf("domain not found")
|
||||
}
|
||||
|
||||
return &hostedZoneInfo{
|
||||
domainID: domains[0].ID,
|
||||
resourceName: strings.TrimSuffix(fqdn, "."+authZone),
|
||||
}, nil
|
||||
}
|
52
vendor/github.com/go-acme/lego/providers/dns/mydnsjp/client.go
generated
vendored
Normal file
52
vendor/github.com/go-acme/lego/providers/dns/mydnsjp/client.go
generated
vendored
Normal file
|
@ -0,0 +1,52 @@
|
|||
package mydnsjp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (d *DNSProvider) doRequest(domain, value string, cmd string) error {
|
||||
req, err := d.buildRequest(domain, value, cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := d.config.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error querying API: %v", err)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode >= 400 {
|
||||
var content []byte
|
||||
content, err = ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return fmt.Errorf("request %s failed [status code %d]: %s", req.URL, resp.StatusCode, string(content))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) buildRequest(domain, value string, cmd string) (*http.Request, error) {
|
||||
params := url.Values{}
|
||||
params.Set("CERTBOT_DOMAIN", domain)
|
||||
params.Set("CERTBOT_VALIDATION", value)
|
||||
params.Set("EDIT_CMD", cmd)
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, defaultBaseURL, strings.NewReader(params.Encode()))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid request: %v", err)
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
req.SetBasicAuth(d.config.MasterID, d.config.Password)
|
||||
|
||||
return req, nil
|
||||
}
|
93
vendor/github.com/go-acme/lego/providers/dns/mydnsjp/mydnsjp.go
generated
vendored
Normal file
93
vendor/github.com/go-acme/lego/providers/dns/mydnsjp/mydnsjp.go
generated
vendored
Normal file
|
@ -0,0 +1,93 @@
|
|||
// Package mydnsjp implements a DNS provider for solving the DNS-01 challenge using MyDNS.jp.
|
||||
package mydnsjp
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
)
|
||||
|
||||
const defaultBaseURL = "https://www.mydns.jp/directedit.html"
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
MasterID string
|
||||
Password string
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
PropagationTimeout: env.GetOrDefaultSecond("MYDNSJP_PROPAGATION_TIMEOUT", 2*time.Minute),
|
||||
PollingInterval: env.GetOrDefaultSecond("MYDNSJP_POLLING_INTERVAL", 2*time.Second),
|
||||
HTTPClient: &http.Client{
|
||||
Timeout: env.GetOrDefaultSecond("MYDNSJP_HTTP_TIMEOUT", 30*time.Second),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for MyDNS.jp.
|
||||
// Credentials must be passed in the environment variables: MYDNSJP_MASTER_ID and MYDNSJP_PASSWORD.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("MYDNSJP_MASTER_ID", "MYDNSJP_PASSWORD")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("mydnsjp: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.MasterID = values["MYDNSJP_MASTER_ID"]
|
||||
config.Password = values["MYDNSJP_PASSWORD"]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for MyDNS.jp.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("mydnsjp: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if config.MasterID == "" || config.Password == "" {
|
||||
return nil, errors.New("mydnsjp: some credentials information are missing")
|
||||
}
|
||||
|
||||
return &DNSProvider{config: config}, nil
|
||||
}
|
||||
|
||||
// Timeout returns the timeout and interval to use when checking for DNS propagation.
|
||||
// Adjusting here to cope with spikes in propagation times.
|
||||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
_, value := dns01.GetRecord(domain, keyAuth)
|
||||
err := d.doRequest(domain, value, "REGIST")
|
||||
if err != nil {
|
||||
return fmt.Errorf("mydnsjp: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
_, value := dns01.GetRecord(domain, keyAuth)
|
||||
err := d.doRequest(domain, value, "DELETE")
|
||||
if err != nil {
|
||||
return fmt.Errorf("mydnsjp: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
225
vendor/github.com/go-acme/lego/providers/dns/namecheap/client.go
generated
vendored
Normal file
225
vendor/github.com/go-acme/lego/providers/dns/namecheap/client.go
generated
vendored
Normal file
|
@ -0,0 +1,225 @@
|
|||
package namecheap
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Record describes a DNS record returned by the Namecheap DNS gethosts API.
|
||||
// Namecheap uses the term "host" to refer to all DNS records that include
|
||||
// a host field (A, AAAA, CNAME, NS, TXT, URL).
|
||||
type Record struct {
|
||||
Type string `xml:",attr"`
|
||||
Name string `xml:",attr"`
|
||||
Address string `xml:",attr"`
|
||||
MXPref string `xml:",attr"`
|
||||
TTL string `xml:",attr"`
|
||||
}
|
||||
|
||||
// apiError describes an error record in a namecheap API response.
|
||||
type apiError struct {
|
||||
Number int `xml:",attr"`
|
||||
Description string `xml:",innerxml"`
|
||||
}
|
||||
|
||||
type setHostsResponse struct {
|
||||
XMLName xml.Name `xml:"ApiResponse"`
|
||||
Status string `xml:"Status,attr"`
|
||||
Errors []apiError `xml:"Errors>Error"`
|
||||
Result struct {
|
||||
IsSuccess string `xml:",attr"`
|
||||
} `xml:"CommandResponse>DomainDNSSetHostsResult"`
|
||||
}
|
||||
|
||||
type getHostsResponse struct {
|
||||
XMLName xml.Name `xml:"ApiResponse"`
|
||||
Status string `xml:"Status,attr"`
|
||||
Errors []apiError `xml:"Errors>Error"`
|
||||
Hosts []Record `xml:"CommandResponse>DomainDNSGetHostsResult>host"`
|
||||
}
|
||||
|
||||
type getTldsResponse struct {
|
||||
XMLName xml.Name `xml:"ApiResponse"`
|
||||
Errors []apiError `xml:"Errors>Error"`
|
||||
Result []struct {
|
||||
Name string `xml:",attr"`
|
||||
} `xml:"CommandResponse>Tlds>Tld"`
|
||||
}
|
||||
|
||||
// getTLDs requests the list of available TLDs.
|
||||
// https://www.namecheap.com/support/api/methods/domains/get-tld-list.aspx
|
||||
func (d *DNSProvider) getTLDs() (map[string]string, error) {
|
||||
request, err := d.newRequestGet("namecheap.domains.getTldList")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var gtr getTldsResponse
|
||||
err = d.do(request, >r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(gtr.Errors) > 0 {
|
||||
return nil, fmt.Errorf("%s [%d]", gtr.Errors[0].Description, gtr.Errors[0].Number)
|
||||
}
|
||||
|
||||
tlds := make(map[string]string)
|
||||
for _, t := range gtr.Result {
|
||||
tlds[t.Name] = t.Name
|
||||
}
|
||||
return tlds, nil
|
||||
}
|
||||
|
||||
// getHosts reads the full list of DNS host records.
|
||||
// https://www.namecheap.com/support/api/methods/domains-dns/get-hosts.aspx
|
||||
func (d *DNSProvider) getHosts(sld, tld string) ([]Record, error) {
|
||||
request, err := d.newRequestGet("namecheap.domains.dns.getHosts",
|
||||
addParam("SLD", sld),
|
||||
addParam("TLD", tld),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var ghr getHostsResponse
|
||||
err = d.do(request, &ghr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(ghr.Errors) > 0 {
|
||||
return nil, fmt.Errorf("%s [%d]", ghr.Errors[0].Description, ghr.Errors[0].Number)
|
||||
}
|
||||
|
||||
return ghr.Hosts, nil
|
||||
}
|
||||
|
||||
// setHosts writes the full list of DNS host records .
|
||||
// https://www.namecheap.com/support/api/methods/domains-dns/set-hosts.aspx
|
||||
func (d *DNSProvider) setHosts(sld, tld string, hosts []Record) error {
|
||||
req, err := d.newRequestPost("namecheap.domains.dns.setHosts",
|
||||
addParam("SLD", sld),
|
||||
addParam("TLD", tld),
|
||||
func(values url.Values) {
|
||||
for i, h := range hosts {
|
||||
ind := fmt.Sprintf("%d", i+1)
|
||||
values.Add("HostName"+ind, h.Name)
|
||||
values.Add("RecordType"+ind, h.Type)
|
||||
values.Add("Address"+ind, h.Address)
|
||||
values.Add("MXPref"+ind, h.MXPref)
|
||||
values.Add("TTL"+ind, h.TTL)
|
||||
}
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var shr setHostsResponse
|
||||
err = d.do(req, &shr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(shr.Errors) > 0 {
|
||||
return fmt.Errorf("%s [%d]", shr.Errors[0].Description, shr.Errors[0].Number)
|
||||
}
|
||||
if shr.Result.IsSuccess != "true" {
|
||||
return fmt.Errorf("setHosts failed")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) do(req *http.Request, out interface{}) error {
|
||||
resp, err := d.config.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode >= 400 {
|
||||
var body []byte
|
||||
body, err = readBody(resp)
|
||||
if err != nil {
|
||||
return fmt.Errorf("HTTP error %d [%s]: %v", resp.StatusCode, http.StatusText(resp.StatusCode), err)
|
||||
}
|
||||
return fmt.Errorf("HTTP error %d [%s]: %s", resp.StatusCode, http.StatusText(resp.StatusCode), string(body))
|
||||
}
|
||||
|
||||
body, err := readBody(resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := xml.Unmarshal(body, out); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) newRequestGet(cmd string, params ...func(url.Values)) (*http.Request, error) {
|
||||
query := d.makeQuery(cmd, params...)
|
||||
|
||||
reqURL, err := url.Parse(d.config.BaseURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reqURL.RawQuery = query.Encode()
|
||||
|
||||
return http.NewRequest(http.MethodGet, reqURL.String(), nil)
|
||||
}
|
||||
|
||||
func (d *DNSProvider) newRequestPost(cmd string, params ...func(url.Values)) (*http.Request, error) {
|
||||
query := d.makeQuery(cmd, params...)
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, d.config.BaseURL, strings.NewReader(query.Encode()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) makeQuery(cmd string, params ...func(url.Values)) url.Values {
|
||||
queryParams := make(url.Values)
|
||||
queryParams.Set("ApiUser", d.config.APIUser)
|
||||
queryParams.Set("ApiKey", d.config.APIKey)
|
||||
queryParams.Set("UserName", d.config.APIUser)
|
||||
queryParams.Set("Command", cmd)
|
||||
queryParams.Set("ClientIp", d.config.ClientIP)
|
||||
|
||||
for _, param := range params {
|
||||
param(queryParams)
|
||||
}
|
||||
|
||||
return queryParams
|
||||
}
|
||||
|
||||
func addParam(key, value string) func(url.Values) {
|
||||
return func(values url.Values) {
|
||||
values.Set(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
func readBody(resp *http.Response) ([]byte, error) {
|
||||
if resp.Body == nil {
|
||||
return nil, fmt.Errorf("response body is nil")
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
rawBody, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return rawBody, nil
|
||||
}
|
260
vendor/github.com/go-acme/lego/providers/dns/namecheap/namecheap.go
generated
vendored
Normal file
260
vendor/github.com/go-acme/lego/providers/dns/namecheap/namecheap.go
generated
vendored
Normal file
|
@ -0,0 +1,260 @@
|
|||
// Package namecheap implements a DNS provider for solving the DNS-01 challenge using namecheap DNS.
|
||||
package namecheap
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/log"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
)
|
||||
|
||||
// Notes about namecheap's tool API:
|
||||
// 1. Using the API requires registration. Once registered, use your account
|
||||
// name and API key to access the API.
|
||||
// 2. There is no API to add or modify a single DNS record. Instead you must
|
||||
// read the entire list of records, make modifications, and then write the
|
||||
// entire updated list of records. (Yuck.)
|
||||
// 3. Namecheap's DNS updates can be slow to propagate. I've seen them take
|
||||
// as long as an hour.
|
||||
// 4. Namecheap requires you to whitelist the IP address from which you call
|
||||
// its APIs. It also requires all API calls to include the whitelisted IP
|
||||
// address as a form or query string value. This code uses a namecheap
|
||||
// service to query the client's IP address.
|
||||
|
||||
const (
|
||||
defaultBaseURL = "https://api.namecheap.com/xml.response"
|
||||
getIPURL = "https://dynamicdns.park-your-domain.com/getip"
|
||||
)
|
||||
|
||||
// A challenge represents all the data needed to specify a dns-01 challenge
|
||||
// to lets-encrypt.
|
||||
type challenge struct {
|
||||
domain string
|
||||
key string
|
||||
keyFqdn string
|
||||
keyValue string
|
||||
tld string
|
||||
sld string
|
||||
host string
|
||||
}
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
Debug bool
|
||||
BaseURL string
|
||||
APIUser string
|
||||
APIKey string
|
||||
ClientIP string
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
BaseURL: defaultBaseURL,
|
||||
Debug: env.GetOrDefaultBool("NAMECHEAP_DEBUG", false),
|
||||
TTL: env.GetOrDefaultInt("NAMECHEAP_TTL", dns01.DefaultTTL),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("NAMECHEAP_PROPAGATION_TIMEOUT", 60*time.Minute),
|
||||
PollingInterval: env.GetOrDefaultSecond("NAMECHEAP_POLLING_INTERVAL", 15*time.Second),
|
||||
HTTPClient: &http.Client{
|
||||
Timeout: env.GetOrDefaultSecond("NAMECHEAP_HTTP_TIMEOUT", 60*time.Second),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the ChallengeProviderTimeout interface
|
||||
// that uses Namecheap's tool API to manage TXT records for a domain.
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for namecheap.
|
||||
// Credentials must be passed in the environment variables:
|
||||
// NAMECHEAP_API_USER and NAMECHEAP_API_KEY.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("NAMECHEAP_API_USER", "NAMECHEAP_API_KEY")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("namecheap: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.APIUser = values["NAMECHEAP_API_USER"]
|
||||
config.APIKey = values["NAMECHEAP_API_KEY"]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for namecheap.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("namecheap: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if config.APIUser == "" || config.APIKey == "" {
|
||||
return nil, fmt.Errorf("namecheap: credentials missing")
|
||||
}
|
||||
|
||||
if len(config.ClientIP) == 0 {
|
||||
clientIP, err := getClientIP(config.HTTPClient, config.Debug)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("namecheap: %v", err)
|
||||
}
|
||||
config.ClientIP = clientIP
|
||||
}
|
||||
|
||||
return &DNSProvider{config: config}, nil
|
||||
}
|
||||
|
||||
// Timeout returns the timeout and interval to use when checking for DNS propagation.
|
||||
// Namecheap can sometimes take a long time to complete an update, so wait up to 60 minutes for the update to propagate.
|
||||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
// Present installs a TXT record for the DNS challenge.
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
tlds, err := d.getTLDs()
|
||||
if err != nil {
|
||||
return fmt.Errorf("namecheap: %v", err)
|
||||
}
|
||||
|
||||
ch, err := newChallenge(domain, keyAuth, tlds)
|
||||
if err != nil {
|
||||
return fmt.Errorf("namecheap: %v", err)
|
||||
}
|
||||
|
||||
records, err := d.getHosts(ch.sld, ch.tld)
|
||||
if err != nil {
|
||||
return fmt.Errorf("namecheap: %v", err)
|
||||
}
|
||||
|
||||
record := Record{
|
||||
Name: ch.key,
|
||||
Type: "TXT",
|
||||
Address: ch.keyValue,
|
||||
MXPref: "10",
|
||||
TTL: strconv.Itoa(d.config.TTL),
|
||||
}
|
||||
|
||||
records = append(records, record)
|
||||
|
||||
if d.config.Debug {
|
||||
for _, h := range records {
|
||||
log.Printf("%-5.5s %-30.30s %-6s %-70.70s", h.Type, h.Name, h.TTL, h.Address)
|
||||
}
|
||||
}
|
||||
|
||||
err = d.setHosts(ch.sld, ch.tld, records)
|
||||
if err != nil {
|
||||
return fmt.Errorf("namecheap: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes a TXT record used for a previous DNS challenge.
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
tlds, err := d.getTLDs()
|
||||
if err != nil {
|
||||
return fmt.Errorf("namecheap: %v", err)
|
||||
}
|
||||
|
||||
ch, err := newChallenge(domain, keyAuth, tlds)
|
||||
if err != nil {
|
||||
return fmt.Errorf("namecheap: %v", err)
|
||||
}
|
||||
|
||||
records, err := d.getHosts(ch.sld, ch.tld)
|
||||
if err != nil {
|
||||
return fmt.Errorf("namecheap: %v", err)
|
||||
}
|
||||
|
||||
// Find the challenge TXT record and remove it if found.
|
||||
var found bool
|
||||
var newRecords []Record
|
||||
for _, h := range records {
|
||||
if h.Name == ch.key && h.Type == "TXT" {
|
||||
found = true
|
||||
} else {
|
||||
newRecords = append(newRecords, h)
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return nil
|
||||
}
|
||||
|
||||
err = d.setHosts(ch.sld, ch.tld, newRecords)
|
||||
if err != nil {
|
||||
return fmt.Errorf("namecheap: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// getClientIP returns the client's public IP address.
|
||||
// It uses namecheap's IP discovery service to perform the lookup.
|
||||
func getClientIP(client *http.Client, debug bool) (addr string, err error) {
|
||||
resp, err := client.Get(getIPURL)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
clientIP, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if debug {
|
||||
log.Println("Client IP:", string(clientIP))
|
||||
}
|
||||
return string(clientIP), nil
|
||||
}
|
||||
|
||||
// newChallenge builds a challenge record from a domain name, a challenge
|
||||
// authentication key, and a map of available TLDs.
|
||||
func newChallenge(domain, keyAuth string, tlds map[string]string) (*challenge, error) {
|
||||
domain = dns01.UnFqdn(domain)
|
||||
parts := strings.Split(domain, ".")
|
||||
|
||||
// Find the longest matching TLD.
|
||||
longest := -1
|
||||
for i := len(parts); i > 0; i-- {
|
||||
t := strings.Join(parts[i-1:], ".")
|
||||
if _, found := tlds[t]; found {
|
||||
longest = i - 1
|
||||
}
|
||||
}
|
||||
if longest < 1 {
|
||||
return nil, fmt.Errorf("invalid domain name %q", domain)
|
||||
}
|
||||
|
||||
tld := strings.Join(parts[longest:], ".")
|
||||
sld := parts[longest-1]
|
||||
|
||||
var host string
|
||||
if longest >= 1 {
|
||||
host = strings.Join(parts[:longest-1], ".")
|
||||
}
|
||||
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
return &challenge{
|
||||
domain: domain,
|
||||
key: "_acme-challenge." + host,
|
||||
keyFqdn: fqdn,
|
||||
keyValue: value,
|
||||
tld: tld,
|
||||
sld: sld,
|
||||
host: host,
|
||||
}, nil
|
||||
}
|
170
vendor/github.com/go-acme/lego/providers/dns/namedotcom/namedotcom.go
generated
vendored
Normal file
170
vendor/github.com/go-acme/lego/providers/dns/namedotcom/namedotcom.go
generated
vendored
Normal file
|
@ -0,0 +1,170 @@
|
|||
// Package namedotcom implements a DNS provider for solving the DNS-01 challenge using Name.com's DNS service.
|
||||
package namedotcom
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
"github.com/namedotcom/go/namecom"
|
||||
)
|
||||
|
||||
// according to https://www.name.com/api-docs/DNS#CreateRecord
|
||||
const minTTL = 300
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
Username string
|
||||
APIToken string
|
||||
Server string
|
||||
TTL int
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt("NAMECOM_TTL", minTTL),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("NAMECOM_PROPAGATION_TIMEOUT", 15*time.Minute),
|
||||
PollingInterval: env.GetOrDefaultSecond("NAMECOM_POLLING_INTERVAL", 20*time.Second),
|
||||
HTTPClient: &http.Client{
|
||||
Timeout: env.GetOrDefaultSecond("NAMECOM_HTTP_TIMEOUT", 10*time.Second),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface.
|
||||
type DNSProvider struct {
|
||||
client *namecom.NameCom
|
||||
config *Config
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for namedotcom.
|
||||
// Credentials must be passed in the environment variables:
|
||||
// NAMECOM_USERNAME and NAMECOM_API_TOKEN
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("NAMECOM_USERNAME", "NAMECOM_API_TOKEN")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("namedotcom: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.Username = values["NAMECOM_USERNAME"]
|
||||
config.APIToken = values["NAMECOM_API_TOKEN"]
|
||||
config.Server = env.GetOrFile("NAMECOM_SERVER")
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for namedotcom.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("namedotcom: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if config.Username == "" {
|
||||
return nil, fmt.Errorf("namedotcom: username is required")
|
||||
}
|
||||
|
||||
if config.APIToken == "" {
|
||||
return nil, fmt.Errorf("namedotcom: API token is required")
|
||||
}
|
||||
|
||||
if config.TTL < minTTL {
|
||||
return nil, fmt.Errorf("namedotcom: invalid TTL, TTL (%d) must be greater than %d", config.TTL, minTTL)
|
||||
}
|
||||
|
||||
client := namecom.New(config.Username, config.APIToken)
|
||||
client.Client = config.HTTPClient
|
||||
|
||||
if config.Server != "" {
|
||||
client.Server = config.Server
|
||||
}
|
||||
|
||||
return &DNSProvider{client: client, config: config}, nil
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge.
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
request := &namecom.Record{
|
||||
DomainName: domain,
|
||||
Host: d.extractRecordName(fqdn, domain),
|
||||
Type: "TXT",
|
||||
TTL: uint32(d.config.TTL),
|
||||
Answer: value,
|
||||
}
|
||||
|
||||
_, err := d.client.CreateRecord(request)
|
||||
if err != nil {
|
||||
return fmt.Errorf("namedotcom: API call failed: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters.
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
records, err := d.getRecords(domain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("namedotcom: %v", err)
|
||||
}
|
||||
|
||||
for _, rec := range records {
|
||||
if rec.Fqdn == fqdn && rec.Type == "TXT" {
|
||||
request := &namecom.DeleteRecordRequest{
|
||||
DomainName: domain,
|
||||
ID: rec.ID,
|
||||
}
|
||||
_, err := d.client.DeleteRecord(request)
|
||||
if err != nil {
|
||||
return fmt.Errorf("namedotcom: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Timeout returns the timeout and interval to use when checking for DNS propagation.
|
||||
// Adjusting here to cope with spikes in propagation times.
|
||||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
func (d *DNSProvider) getRecords(domain string) ([]*namecom.Record, error) {
|
||||
request := &namecom.ListRecordsRequest{
|
||||
DomainName: domain,
|
||||
Page: 1,
|
||||
}
|
||||
|
||||
var records []*namecom.Record
|
||||
for request.Page > 0 {
|
||||
response, err := d.client.ListRecords(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
records = append(records, response.Records...)
|
||||
request.Page = response.NextPage
|
||||
}
|
||||
|
||||
return records, nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) extractRecordName(fqdn, domain string) string {
|
||||
name := dns01.UnFqdn(fqdn)
|
||||
if idx := strings.Index(name, "."+domain); idx != -1 {
|
||||
return name[:idx]
|
||||
}
|
||||
return name
|
||||
}
|
327
vendor/github.com/go-acme/lego/providers/dns/netcup/internal/client.go
generated
vendored
Normal file
327
vendor/github.com/go-acme/lego/providers/dns/netcup/internal/client.go
generated
vendored
Normal file
|
@ -0,0 +1,327 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// defaultBaseURL for reaching the jSON-based API-Endpoint of netcup
|
||||
const defaultBaseURL = "https://ccp.netcup.net/run/webservice/servers/endpoint.php?JSON"
|
||||
|
||||
// success response status
|
||||
const success = "success"
|
||||
|
||||
// Request wrapper as specified in netcup wiki
|
||||
// needed for every request to netcup API around *Msg
|
||||
// https://www.netcup-wiki.de/wiki/CCP_API#Anmerkungen_zu_JSON-Requests
|
||||
type Request struct {
|
||||
Action string `json:"action"`
|
||||
Param interface{} `json:"param"`
|
||||
}
|
||||
|
||||
// LoginRequest as specified in netcup WSDL
|
||||
// https://ccp.netcup.net/run/webservice/servers/endpoint.php#login
|
||||
type LoginRequest struct {
|
||||
CustomerNumber string `json:"customernumber"`
|
||||
APIKey string `json:"apikey"`
|
||||
APIPassword string `json:"apipassword"`
|
||||
ClientRequestID string `json:"clientrequestid,omitempty"`
|
||||
}
|
||||
|
||||
// LogoutRequest as specified in netcup WSDL
|
||||
// https://ccp.netcup.net/run/webservice/servers/endpoint.php#logout
|
||||
type LogoutRequest struct {
|
||||
CustomerNumber string `json:"customernumber"`
|
||||
APIKey string `json:"apikey"`
|
||||
APISessionID string `json:"apisessionid"`
|
||||
ClientRequestID string `json:"clientrequestid,omitempty"`
|
||||
}
|
||||
|
||||
// UpdateDNSRecordsRequest as specified in netcup WSDL
|
||||
// https://ccp.netcup.net/run/webservice/servers/endpoint.php#updateDnsRecords
|
||||
type UpdateDNSRecordsRequest struct {
|
||||
DomainName string `json:"domainname"`
|
||||
CustomerNumber string `json:"customernumber"`
|
||||
APIKey string `json:"apikey"`
|
||||
APISessionID string `json:"apisessionid"`
|
||||
ClientRequestID string `json:"clientrequestid,omitempty"`
|
||||
DNSRecordSet DNSRecordSet `json:"dnsrecordset"`
|
||||
}
|
||||
|
||||
// DNSRecordSet as specified in netcup WSDL
|
||||
// needed in UpdateDNSRecordsRequest
|
||||
// https://ccp.netcup.net/run/webservice/servers/endpoint.php#Dnsrecordset
|
||||
type DNSRecordSet struct {
|
||||
DNSRecords []DNSRecord `json:"dnsrecords"`
|
||||
}
|
||||
|
||||
// InfoDNSRecordsRequest as specified in netcup WSDL
|
||||
// https://ccp.netcup.net/run/webservice/servers/endpoint.php#infoDnsRecords
|
||||
type InfoDNSRecordsRequest struct {
|
||||
DomainName string `json:"domainname"`
|
||||
CustomerNumber string `json:"customernumber"`
|
||||
APIKey string `json:"apikey"`
|
||||
APISessionID string `json:"apisessionid"`
|
||||
ClientRequestID string `json:"clientrequestid,omitempty"`
|
||||
}
|
||||
|
||||
// DNSRecord as specified in netcup WSDL
|
||||
// https://ccp.netcup.net/run/webservice/servers/endpoint.php#Dnsrecord
|
||||
type DNSRecord struct {
|
||||
ID int `json:"id,string,omitempty"`
|
||||
Hostname string `json:"hostname"`
|
||||
RecordType string `json:"type"`
|
||||
Priority string `json:"priority,omitempty"`
|
||||
Destination string `json:"destination"`
|
||||
DeleteRecord bool `json:"deleterecord,omitempty"`
|
||||
State string `json:"state,omitempty"`
|
||||
TTL int `json:"ttl,omitempty"`
|
||||
}
|
||||
|
||||
// ResponseMsg as specified in netcup WSDL
|
||||
// https://ccp.netcup.net/run/webservice/servers/endpoint.php#Responsemessage
|
||||
type ResponseMsg struct {
|
||||
ServerRequestID string `json:"serverrequestid"`
|
||||
ClientRequestID string `json:"clientrequestid,omitempty"`
|
||||
Action string `json:"action"`
|
||||
Status string `json:"status"`
|
||||
StatusCode int `json:"statuscode"`
|
||||
ShortMessage string `json:"shortmessage"`
|
||||
LongMessage string `json:"longmessage"`
|
||||
ResponseData json.RawMessage `json:"responsedata,omitempty"`
|
||||
}
|
||||
|
||||
func (r *ResponseMsg) Error() string {
|
||||
return fmt.Sprintf("an error occurred during the action %s: [Status=%s, StatusCode=%d, ShortMessage=%s, LongMessage=%s]",
|
||||
r.Action, r.Status, r.StatusCode, r.ShortMessage, r.LongMessage)
|
||||
}
|
||||
|
||||
// LoginResponse response to login action.
|
||||
type LoginResponse struct {
|
||||
APISessionID string `json:"apisessionid"`
|
||||
}
|
||||
|
||||
// InfoDNSRecordsResponse response to infoDnsRecords action.
|
||||
type InfoDNSRecordsResponse struct {
|
||||
APISessionID string `json:"apisessionid"`
|
||||
DNSRecords []DNSRecord `json:"dnsrecords,omitempty"`
|
||||
}
|
||||
|
||||
// Client netcup DNS client
|
||||
type Client struct {
|
||||
customerNumber string
|
||||
apiKey string
|
||||
apiPassword string
|
||||
HTTPClient *http.Client
|
||||
BaseURL string
|
||||
}
|
||||
|
||||
// NewClient creates a netcup DNS client
|
||||
func NewClient(customerNumber string, apiKey string, apiPassword string) (*Client, error) {
|
||||
if customerNumber == "" || apiKey == "" || apiPassword == "" {
|
||||
return nil, fmt.Errorf("credentials missing")
|
||||
}
|
||||
|
||||
return &Client{
|
||||
customerNumber: customerNumber,
|
||||
apiKey: apiKey,
|
||||
apiPassword: apiPassword,
|
||||
BaseURL: defaultBaseURL,
|
||||
HTTPClient: &http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Login performs the login as specified by the netcup WSDL
|
||||
// returns sessionID needed to perform remaining actions
|
||||
// https://ccp.netcup.net/run/webservice/servers/endpoint.php
|
||||
func (c *Client) Login() (string, error) {
|
||||
payload := &Request{
|
||||
Action: "login",
|
||||
Param: &LoginRequest{
|
||||
CustomerNumber: c.customerNumber,
|
||||
APIKey: c.apiKey,
|
||||
APIPassword: c.apiPassword,
|
||||
ClientRequestID: "",
|
||||
},
|
||||
}
|
||||
|
||||
var responseData LoginResponse
|
||||
err := c.doRequest(payload, &responseData)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("loging error: %v", err)
|
||||
}
|
||||
|
||||
return responseData.APISessionID, nil
|
||||
}
|
||||
|
||||
// Logout performs the logout with the supplied sessionID as specified by the netcup WSDL
|
||||
// https://ccp.netcup.net/run/webservice/servers/endpoint.php
|
||||
func (c *Client) Logout(sessionID string) error {
|
||||
payload := &Request{
|
||||
Action: "logout",
|
||||
Param: &LogoutRequest{
|
||||
CustomerNumber: c.customerNumber,
|
||||
APIKey: c.apiKey,
|
||||
APISessionID: sessionID,
|
||||
ClientRequestID: "",
|
||||
},
|
||||
}
|
||||
|
||||
err := c.doRequest(payload, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("logout error: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateDNSRecord performs an update of the DNSRecords as specified by the netcup WSDL
|
||||
// https://ccp.netcup.net/run/webservice/servers/endpoint.php
|
||||
func (c *Client) UpdateDNSRecord(sessionID, domainName string, records []DNSRecord) error {
|
||||
payload := &Request{
|
||||
Action: "updateDnsRecords",
|
||||
Param: UpdateDNSRecordsRequest{
|
||||
DomainName: domainName,
|
||||
CustomerNumber: c.customerNumber,
|
||||
APIKey: c.apiKey,
|
||||
APISessionID: sessionID,
|
||||
ClientRequestID: "",
|
||||
DNSRecordSet: DNSRecordSet{DNSRecords: records},
|
||||
},
|
||||
}
|
||||
|
||||
err := c.doRequest(payload, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error when sending the request: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetDNSRecords retrieves all dns records of an DNS-Zone as specified by the netcup WSDL
|
||||
// returns an array of DNSRecords
|
||||
// https://ccp.netcup.net/run/webservice/servers/endpoint.php
|
||||
func (c *Client) GetDNSRecords(hostname, apiSessionID string) ([]DNSRecord, error) {
|
||||
payload := &Request{
|
||||
Action: "infoDnsRecords",
|
||||
Param: InfoDNSRecordsRequest{
|
||||
DomainName: hostname,
|
||||
CustomerNumber: c.customerNumber,
|
||||
APIKey: c.apiKey,
|
||||
APISessionID: apiSessionID,
|
||||
ClientRequestID: "",
|
||||
},
|
||||
}
|
||||
|
||||
var responseData InfoDNSRecordsResponse
|
||||
err := c.doRequest(payload, &responseData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error when sending the request: %v", err)
|
||||
}
|
||||
|
||||
return responseData.DNSRecords, nil
|
||||
|
||||
}
|
||||
|
||||
// doRequest marshals given body to JSON, send the request to netcup API
|
||||
// and returns body of response
|
||||
func (c *Client) doRequest(payload interface{}, responseData interface{}) error {
|
||||
body, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, c.BaseURL, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.Close = true
|
||||
req.Header.Set("content-type", "application/json")
|
||||
|
||||
resp, err := c.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = checkResponse(resp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
respMsg, err := decodeResponseMsg(resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if respMsg.Status != success {
|
||||
return respMsg
|
||||
}
|
||||
|
||||
if responseData != nil {
|
||||
err = json.Unmarshal(respMsg.ResponseData, responseData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v: unmarshaling %T error: %v: %s",
|
||||
respMsg, responseData, err, string(respMsg.ResponseData))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkResponse(resp *http.Response) error {
|
||||
if resp.StatusCode > 299 {
|
||||
if resp.Body == nil {
|
||||
return fmt.Errorf("response body is nil, status code=%d", resp.StatusCode)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
raw, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to read body: status code=%d, error=%v", resp.StatusCode, err)
|
||||
}
|
||||
|
||||
return fmt.Errorf("status code=%d: %s", resp.StatusCode, string(raw))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func decodeResponseMsg(resp *http.Response) (*ResponseMsg, error) {
|
||||
if resp.Body == nil {
|
||||
return nil, fmt.Errorf("response body is nil, status code=%d", resp.StatusCode)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
raw, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to read body: status code=%d, error=%v", resp.StatusCode, err)
|
||||
}
|
||||
|
||||
var respMsg ResponseMsg
|
||||
err = json.Unmarshal(raw, &respMsg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unmarshaling %T error [status code=%d]: %v: %s", respMsg, resp.StatusCode, err, string(raw))
|
||||
}
|
||||
|
||||
return &respMsg, nil
|
||||
}
|
||||
|
||||
// GetDNSRecordIdx searches a given array of DNSRecords for a given DNSRecord
|
||||
// equivalence is determined by Destination and RecortType attributes
|
||||
// returns index of given DNSRecord in given array of DNSRecords
|
||||
func GetDNSRecordIdx(records []DNSRecord, record DNSRecord) (int, error) {
|
||||
for index, element := range records {
|
||||
if record.Destination == element.Destination && record.RecordType == element.RecordType {
|
||||
return index, nil
|
||||
}
|
||||
}
|
||||
return -1, fmt.Errorf("no DNS Record found")
|
||||
}
|
182
vendor/github.com/go-acme/lego/providers/dns/netcup/netcup.go
generated
vendored
Normal file
182
vendor/github.com/go-acme/lego/providers/dns/netcup/netcup.go
generated
vendored
Normal file
|
@ -0,0 +1,182 @@
|
|||
// Package netcup implements a DNS Provider for solving the DNS-01 challenge using the netcup DNS API.
|
||||
package netcup
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/providers/dns/netcup/internal"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/log"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
Key string
|
||||
Password string
|
||||
Customer string
|
||||
TTL int
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt("NETCUP_TTL", dns01.DefaultTTL),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("NETCUP_PROPAGATION_TIMEOUT", 120*time.Second),
|
||||
PollingInterval: env.GetOrDefaultSecond("NETCUP_POLLING_INTERVAL", 5*time.Second),
|
||||
HTTPClient: &http.Client{
|
||||
Timeout: env.GetOrDefaultSecond("NETCUP_HTTP_TIMEOUT", 10*time.Second),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface
|
||||
type DNSProvider struct {
|
||||
client *internal.Client
|
||||
config *Config
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for netcup.
|
||||
// Credentials must be passed in the environment variables:
|
||||
// NETCUP_CUSTOMER_NUMBER, NETCUP_API_KEY, NETCUP_API_PASSWORD
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("NETCUP_CUSTOMER_NUMBER", "NETCUP_API_KEY", "NETCUP_API_PASSWORD")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("netcup: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.Customer = values["NETCUP_CUSTOMER_NUMBER"]
|
||||
config.Key = values["NETCUP_API_KEY"]
|
||||
config.Password = values["NETCUP_API_PASSWORD"]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for netcup.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("netcup: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
client, err := internal.NewClient(config.Customer, config.Key, config.Password)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("netcup: %v", err)
|
||||
}
|
||||
|
||||
client.HTTPClient = config.HTTPClient
|
||||
|
||||
return &DNSProvider{client: client, config: config}, nil
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge
|
||||
func (d *DNSProvider) Present(domainName, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domainName, keyAuth)
|
||||
|
||||
zone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("netcup: failed to find DNSZone, %v", err)
|
||||
}
|
||||
|
||||
sessionID, err := d.client.Login()
|
||||
if err != nil {
|
||||
return fmt.Errorf("netcup: %v", err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err = d.client.Logout(sessionID)
|
||||
if err != nil {
|
||||
log.Print("netcup: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
hostname := strings.Replace(fqdn, "."+zone, "", 1)
|
||||
record := internal.DNSRecord{
|
||||
Hostname: hostname,
|
||||
RecordType: "TXT",
|
||||
Destination: value,
|
||||
TTL: d.config.TTL,
|
||||
}
|
||||
|
||||
zone = dns01.UnFqdn(zone)
|
||||
|
||||
records, err := d.client.GetDNSRecords(zone, sessionID)
|
||||
if err != nil {
|
||||
// skip no existing records
|
||||
log.Infof("no existing records, error ignored: %v", err)
|
||||
}
|
||||
|
||||
records = append(records, record)
|
||||
|
||||
err = d.client.UpdateDNSRecord(sessionID, zone, records)
|
||||
if err != nil {
|
||||
return fmt.Errorf("netcup: failed to add TXT-Record: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters
|
||||
func (d *DNSProvider) CleanUp(domainName, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domainName, keyAuth)
|
||||
|
||||
zone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("netcup: failed to find DNSZone, %v", err)
|
||||
}
|
||||
|
||||
sessionID, err := d.client.Login()
|
||||
if err != nil {
|
||||
return fmt.Errorf("netcup: %v", err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err = d.client.Logout(sessionID)
|
||||
if err != nil {
|
||||
log.Print("netcup: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
hostname := strings.Replace(fqdn, "."+zone, "", 1)
|
||||
|
||||
zone = dns01.UnFqdn(zone)
|
||||
|
||||
records, err := d.client.GetDNSRecords(zone, sessionID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("netcup: %v", err)
|
||||
}
|
||||
|
||||
record := internal.DNSRecord{
|
||||
Hostname: hostname,
|
||||
RecordType: "TXT",
|
||||
Destination: value,
|
||||
}
|
||||
|
||||
idx, err := internal.GetDNSRecordIdx(records, record)
|
||||
if err != nil {
|
||||
return fmt.Errorf("netcup: %v", err)
|
||||
}
|
||||
|
||||
records[idx].DeleteRecord = true
|
||||
|
||||
err = d.client.UpdateDNSRecord(sessionID, zone, []internal.DNSRecord{records[idx]})
|
||||
if err != nil {
|
||||
return fmt.Errorf("netcup: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Timeout returns the timeout and interval to use when checking for DNS propagation.
|
||||
// Adjusting here to cope with spikes in propagation times.
|
||||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
234
vendor/github.com/go-acme/lego/providers/dns/nifcloud/internal/client.go
generated
vendored
Normal file
234
vendor/github.com/go-acme/lego/providers/dns/nifcloud/internal/client.go
generated
vendored
Normal file
|
@ -0,0 +1,234 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultBaseURL = "https://dns.api.nifcloud.com"
|
||||
apiVersion = "2012-12-12N2013-12-16"
|
||||
// XMLNs XML NS of Route53
|
||||
XMLNs = "https://route53.amazonaws.com/doc/2012-12-12/"
|
||||
)
|
||||
|
||||
// ChangeResourceRecordSetsRequest is a complex type that contains change information for the resource record set.
|
||||
type ChangeResourceRecordSetsRequest struct {
|
||||
XMLNs string `xml:"xmlns,attr"`
|
||||
ChangeBatch ChangeBatch `xml:"ChangeBatch"`
|
||||
}
|
||||
|
||||
// ChangeResourceRecordSetsResponse is a complex type containing the response for the request.
|
||||
type ChangeResourceRecordSetsResponse struct {
|
||||
ChangeInfo ChangeInfo `xml:"ChangeInfo"`
|
||||
}
|
||||
|
||||
// GetChangeResponse is a complex type that contains the ChangeInfo element.
|
||||
type GetChangeResponse struct {
|
||||
ChangeInfo ChangeInfo `xml:"ChangeInfo"`
|
||||
}
|
||||
|
||||
// ErrorResponse is the information for any errors.
|
||||
type ErrorResponse struct {
|
||||
Error struct {
|
||||
Type string `xml:"Type"`
|
||||
Message string `xml:"Message"`
|
||||
Code string `xml:"Code"`
|
||||
} `xml:"Error"`
|
||||
RequestID string `xml:"RequestId"`
|
||||
}
|
||||
|
||||
// ChangeBatch is the information for a change request.
|
||||
type ChangeBatch struct {
|
||||
Changes Changes `xml:"Changes"`
|
||||
Comment string `xml:"Comment"`
|
||||
}
|
||||
|
||||
// Changes is array of Change.
|
||||
type Changes struct {
|
||||
Change []Change `xml:"Change"`
|
||||
}
|
||||
|
||||
// Change is the information for each resource record set that you want to change.
|
||||
type Change struct {
|
||||
Action string `xml:"Action"`
|
||||
ResourceRecordSet ResourceRecordSet `xml:"ResourceRecordSet"`
|
||||
}
|
||||
|
||||
// ResourceRecordSet is the information about the resource record set to create or delete.
|
||||
type ResourceRecordSet struct {
|
||||
Name string `xml:"Name"`
|
||||
Type string `xml:"Type"`
|
||||
TTL int `xml:"TTL"`
|
||||
ResourceRecords ResourceRecords `xml:"ResourceRecords"`
|
||||
}
|
||||
|
||||
// ResourceRecords is array of ResourceRecord.
|
||||
type ResourceRecords struct {
|
||||
ResourceRecord []ResourceRecord `xml:"ResourceRecord"`
|
||||
}
|
||||
|
||||
// ResourceRecord is the information specific to the resource record.
|
||||
type ResourceRecord struct {
|
||||
Value string `xml:"Value"`
|
||||
}
|
||||
|
||||
// ChangeInfo is A complex type that describes change information about changes made to your hosted zone.
|
||||
type ChangeInfo struct {
|
||||
ID string `xml:"Id"`
|
||||
Status string `xml:"Status"`
|
||||
SubmittedAt string `xml:"SubmittedAt"`
|
||||
}
|
||||
|
||||
// NewClient Creates a new client of NIFCLOUD DNS
|
||||
func NewClient(accessKey string, secretKey string) (*Client, error) {
|
||||
if len(accessKey) == 0 || len(secretKey) == 0 {
|
||||
return nil, errors.New("credentials missing")
|
||||
}
|
||||
|
||||
return &Client{
|
||||
accessKey: accessKey,
|
||||
secretKey: secretKey,
|
||||
BaseURL: defaultBaseURL,
|
||||
HTTPClient: &http.Client{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Client client of NIFCLOUD DNS
|
||||
type Client struct {
|
||||
accessKey string
|
||||
secretKey string
|
||||
BaseURL string
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// ChangeResourceRecordSets Call ChangeResourceRecordSets API and return response.
|
||||
func (c *Client) ChangeResourceRecordSets(hostedZoneID string, input ChangeResourceRecordSetsRequest) (*ChangeResourceRecordSetsResponse, error) {
|
||||
requestURL := fmt.Sprintf("%s/%s/hostedzone/%s/rrset", c.BaseURL, apiVersion, hostedZoneID)
|
||||
|
||||
body := &bytes.Buffer{}
|
||||
body.Write([]byte(xml.Header))
|
||||
err := xml.NewEncoder(body).Encode(input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, requestURL, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "text/xml; charset=utf-8")
|
||||
|
||||
err = c.sign(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("an error occurred during the creation of the signature: %v", err)
|
||||
}
|
||||
|
||||
res, err := c.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if res.Body == nil {
|
||||
return nil, errors.New("the response body is nil")
|
||||
}
|
||||
|
||||
defer res.Body.Close()
|
||||
|
||||
if res.StatusCode != http.StatusOK {
|
||||
errResp := &ErrorResponse{}
|
||||
err = xml.NewDecoder(res.Body).Decode(errResp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("an error occurred while unmarshaling the error body to XML: %v", err)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("an error occurred: %s", errResp.Error.Message)
|
||||
}
|
||||
|
||||
output := &ChangeResourceRecordSetsResponse{}
|
||||
err = xml.NewDecoder(res.Body).Decode(output)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("an error occurred while unmarshaling the response body to XML: %v", err)
|
||||
}
|
||||
|
||||
return output, err
|
||||
}
|
||||
|
||||
// GetChange Call GetChange API and return response.
|
||||
func (c *Client) GetChange(statusID string) (*GetChangeResponse, error) {
|
||||
requestURL := fmt.Sprintf("%s/%s/change/%s", c.BaseURL, apiVersion, statusID)
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, requestURL, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = c.sign(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("an error occurred during the creation of the signature: %v", err)
|
||||
}
|
||||
|
||||
res, err := c.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if res.Body == nil {
|
||||
return nil, errors.New("the response body is nil")
|
||||
}
|
||||
|
||||
defer res.Body.Close()
|
||||
|
||||
if res.StatusCode != http.StatusOK {
|
||||
errResp := &ErrorResponse{}
|
||||
err = xml.NewDecoder(res.Body).Decode(errResp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("an error occurred while unmarshaling the error body to XML: %v", err)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("an error occurred: %s", errResp.Error.Message)
|
||||
}
|
||||
|
||||
output := &GetChangeResponse{}
|
||||
err = xml.NewDecoder(res.Body).Decode(output)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("an error occurred while unmarshaling the response body to XML: %v", err)
|
||||
}
|
||||
|
||||
return output, nil
|
||||
}
|
||||
|
||||
func (c *Client) sign(req *http.Request) error {
|
||||
if req.Header.Get("Date") == "" {
|
||||
location, err := time.LoadLocation("GMT")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.Header.Set("Date", time.Now().In(location).Format(time.RFC1123))
|
||||
}
|
||||
|
||||
if req.URL.Path == "" {
|
||||
req.URL.Path += "/"
|
||||
}
|
||||
|
||||
mac := hmac.New(sha1.New, []byte(c.secretKey))
|
||||
_, err := mac.Write([]byte(req.Header.Get("Date")))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hashed := mac.Sum(nil)
|
||||
signature := base64.StdEncoding.EncodeToString(hashed)
|
||||
|
||||
auth := fmt.Sprintf("NIFTY3-HTTPS NiftyAccessKeyId=%s,Algorithm=HmacSHA1,Signature=%s", c.accessKey, signature)
|
||||
req.Header.Set("X-Nifty-Authorization", auth)
|
||||
|
||||
return nil
|
||||
}
|
156
vendor/github.com/go-acme/lego/providers/dns/nifcloud/nifcloud.go
generated
vendored
Normal file
156
vendor/github.com/go-acme/lego/providers/dns/nifcloud/nifcloud.go
generated
vendored
Normal file
|
@ -0,0 +1,156 @@
|
|||
// Package nifcloud implements a DNS provider for solving the DNS-01 challenge using NIFCLOUD DNS.
|
||||
package nifcloud
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/providers/dns/nifcloud/internal"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
"github.com/go-acme/lego/platform/wait"
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
BaseURL string
|
||||
AccessKey string
|
||||
SecretKey string
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt("NIFCLOUD_TTL", dns01.DefaultTTL),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("NIFCLOUD_PROPAGATION_TIMEOUT", dns01.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("NIFCLOUD_POLLING_INTERVAL", dns01.DefaultPollingInterval),
|
||||
HTTPClient: &http.Client{
|
||||
Timeout: env.GetOrDefaultSecond("NIFCLOUD_HTTP_TIMEOUT", 30*time.Second),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider implements the acme.ChallengeProvider interface
|
||||
type DNSProvider struct {
|
||||
client *internal.Client
|
||||
config *Config
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for the NIFCLOUD DNS service.
|
||||
// Credentials must be passed in the environment variables:
|
||||
// NIFCLOUD_ACCESS_KEY_ID and NIFCLOUD_SECRET_ACCESS_KEY.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("NIFCLOUD_ACCESS_KEY_ID", "NIFCLOUD_SECRET_ACCESS_KEY")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("nifcloud: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.BaseURL = env.GetOrFile("NIFCLOUD_DNS_ENDPOINT")
|
||||
config.AccessKey = values["NIFCLOUD_ACCESS_KEY_ID"]
|
||||
config.SecretKey = values["NIFCLOUD_SECRET_ACCESS_KEY"]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for NIFCLOUD.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("nifcloud: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
client, err := internal.NewClient(config.AccessKey, config.SecretKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("nifcloud: %v", err)
|
||||
}
|
||||
|
||||
if config.HTTPClient != nil {
|
||||
client.HTTPClient = config.HTTPClient
|
||||
}
|
||||
|
||||
if len(config.BaseURL) > 0 {
|
||||
client.BaseURL = config.BaseURL
|
||||
}
|
||||
|
||||
return &DNSProvider{client: client, config: config}, nil
|
||||
}
|
||||
|
||||
// Present creates a TXT record using the specified parameters
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
err := d.changeRecord("CREATE", fqdn, value, domain, d.config.TTL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("nifcloud: %v", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
err := d.changeRecord("DELETE", fqdn, value, domain, d.config.TTL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("nifcloud: %v", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Timeout returns the timeout and interval to use when checking for DNS propagation.
|
||||
// Adjusting here to cope with spikes in propagation times.
|
||||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
func (d *DNSProvider) changeRecord(action, fqdn, value, domain string, ttl int) error {
|
||||
name := dns01.UnFqdn(fqdn)
|
||||
|
||||
reqParams := internal.ChangeResourceRecordSetsRequest{
|
||||
XMLNs: internal.XMLNs,
|
||||
ChangeBatch: internal.ChangeBatch{
|
||||
Comment: "Managed by Lego",
|
||||
Changes: internal.Changes{
|
||||
Change: []internal.Change{
|
||||
{
|
||||
Action: action,
|
||||
ResourceRecordSet: internal.ResourceRecordSet{
|
||||
Name: name,
|
||||
Type: "TXT",
|
||||
TTL: ttl,
|
||||
ResourceRecords: internal.ResourceRecords{
|
||||
ResourceRecord: []internal.ResourceRecord{
|
||||
{
|
||||
Value: value,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
resp, err := d.client.ChangeResourceRecordSets(domain, reqParams)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to change NIFCLOUD record set: %v", err)
|
||||
}
|
||||
|
||||
statusID := resp.ChangeInfo.ID
|
||||
|
||||
return wait.For("nifcloud", 120*time.Second, 4*time.Second, func() (bool, error) {
|
||||
resp, err := d.client.GetChange(statusID)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to query NIFCLOUD DNS change status: %v", err)
|
||||
}
|
||||
return resp.ChangeInfo.Status == "INSYNC", nil
|
||||
})
|
||||
}
|
162
vendor/github.com/go-acme/lego/providers/dns/ns1/ns1.go
generated
vendored
Normal file
162
vendor/github.com/go-acme/lego/providers/dns/ns1/ns1.go
generated
vendored
Normal file
|
@ -0,0 +1,162 @@
|
|||
// Package ns1 implements a DNS provider for solving the DNS-01 challenge using NS1 DNS.
|
||||
package ns1
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/log"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
"gopkg.in/ns1/ns1-go.v2/rest"
|
||||
"gopkg.in/ns1/ns1-go.v2/rest/model/dns"
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
APIKey string
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt("NS1_TTL", dns01.DefaultTTL),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("NS1_PROPAGATION_TIMEOUT", dns01.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("NS1_POLLING_INTERVAL", dns01.DefaultPollingInterval),
|
||||
HTTPClient: &http.Client{
|
||||
Timeout: env.GetOrDefaultSecond("NS1_HTTP_TIMEOUT", 10*time.Second),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface.
|
||||
type DNSProvider struct {
|
||||
client *rest.Client
|
||||
config *Config
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for NS1.
|
||||
// Credentials must be passed in the environment variables: NS1_API_KEY.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("NS1_API_KEY")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ns1: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.APIKey = values["NS1_API_KEY"]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for NS1.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("ns1: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if config.APIKey == "" {
|
||||
return nil, fmt.Errorf("ns1: credentials missing")
|
||||
}
|
||||
|
||||
client := rest.NewClient(config.HTTPClient, rest.SetAPIKey(config.APIKey))
|
||||
|
||||
return &DNSProvider{client: client, config: config}, nil
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge.
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
zone, err := d.getHostedZone(fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ns1: %v", err)
|
||||
}
|
||||
|
||||
record, _, err := d.client.Records.Get(zone.Zone, dns01.UnFqdn(fqdn), "TXT")
|
||||
|
||||
// Create a new record
|
||||
if err == rest.ErrRecordMissing || record == nil {
|
||||
log.Infof("Create a new record for [zone: %s, fqdn: %s, domain: %s]", zone.Zone, fqdn)
|
||||
|
||||
record = dns.NewRecord(zone.Zone, dns01.UnFqdn(fqdn), "TXT")
|
||||
record.TTL = d.config.TTL
|
||||
record.Answers = []*dns.Answer{{Rdata: []string{value}}}
|
||||
|
||||
_, err = d.client.Records.Create(record)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ns1: failed to create record [zone: %q, fqdn: %q]: %v", zone.Zone, fqdn, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("ns1: failed to get the existing record: %v", err)
|
||||
}
|
||||
|
||||
// Update the existing records
|
||||
record.Answers = append(record.Answers, &dns.Answer{Rdata: []string{value}})
|
||||
|
||||
log.Infof("Update an existing record for [zone: %s, fqdn: %s, domain: %s]", zone.Zone, fqdn, domain)
|
||||
|
||||
_, err = d.client.Records.Update(record)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ns1: failed to update record [zone: %q, fqdn: %q]: %v", zone.Zone, fqdn, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters.
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
zone, err := d.getHostedZone(fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ns1: %v", err)
|
||||
}
|
||||
|
||||
name := dns01.UnFqdn(fqdn)
|
||||
_, err = d.client.Records.Delete(zone.Zone, name, "TXT")
|
||||
if err != nil {
|
||||
return fmt.Errorf("ns1: failed to delete record [zone: %q, domain: %q]: %v", zone.Zone, name, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Timeout returns the timeout and interval to use when checking for DNS propagation.
|
||||
// Adjusting here to cope with spikes in propagation times.
|
||||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
func (d *DNSProvider) getHostedZone(fqdn string) (*dns.Zone, error) {
|
||||
authZone, err := getAuthZone(fqdn)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to extract auth zone from fqdn %q: %v", fqdn, err)
|
||||
}
|
||||
|
||||
zone, _, err := d.client.Zones.Get(authZone)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get zone [authZone: %q, fqdn: %q]: %v", authZone, fqdn, err)
|
||||
}
|
||||
|
||||
return zone, nil
|
||||
}
|
||||
|
||||
func getAuthZone(fqdn string) (string, error) {
|
||||
authZone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return strings.TrimSuffix(authZone, "."), nil
|
||||
}
|
101
vendor/github.com/go-acme/lego/providers/dns/oraclecloud/configprovider.go
generated
vendored
Normal file
101
vendor/github.com/go-acme/lego/providers/dns/oraclecloud/configprovider.go
generated
vendored
Normal file
|
@ -0,0 +1,101 @@
|
|||
package oraclecloud
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
"github.com/oracle/oci-go-sdk/common"
|
||||
)
|
||||
|
||||
const (
|
||||
ociPrivkey = "OCI_PRIVKEY"
|
||||
ociPrivkeyPass = "OCI_PRIVKEY_PASS"
|
||||
ociTenancyOCID = "OCI_TENANCY_OCID"
|
||||
ociUserOCID = "OCI_USER_OCID"
|
||||
ociPubkeyFingerprint = "OCI_PUBKEY_FINGERPRINT"
|
||||
ociRegion = "OCI_REGION"
|
||||
)
|
||||
|
||||
type configProvider struct {
|
||||
values map[string]string
|
||||
privateKeyPassphrase string
|
||||
}
|
||||
|
||||
func newConfigProvider(values map[string]string) *configProvider {
|
||||
return &configProvider{
|
||||
values: values,
|
||||
privateKeyPassphrase: env.GetOrFile(ociPrivkeyPass),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *configProvider) PrivateRSAKey() (*rsa.PrivateKey, error) {
|
||||
privateKey, err := getPrivateKey(ociPrivkey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return common.PrivateKeyFromBytes(privateKey, common.String(p.privateKeyPassphrase))
|
||||
}
|
||||
|
||||
func (p *configProvider) KeyID() (string, error) {
|
||||
tenancy, err := p.TenancyOCID()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
user, err := p.UserOCID()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
fingerprint, err := p.KeyFingerprint()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s/%s/%s", tenancy, user, fingerprint), nil
|
||||
}
|
||||
|
||||
func (p *configProvider) TenancyOCID() (value string, err error) {
|
||||
return p.values[ociTenancyOCID], nil
|
||||
}
|
||||
|
||||
func (p *configProvider) UserOCID() (string, error) {
|
||||
return p.values[ociUserOCID], nil
|
||||
}
|
||||
|
||||
func (p *configProvider) KeyFingerprint() (string, error) {
|
||||
return p.values[ociPubkeyFingerprint], nil
|
||||
}
|
||||
|
||||
func (p *configProvider) Region() (string, error) {
|
||||
return p.values[ociRegion], nil
|
||||
}
|
||||
|
||||
func getPrivateKey(envVar string) ([]byte, error) {
|
||||
envVarValue := os.Getenv(envVar)
|
||||
if envVarValue != "" {
|
||||
bytes, err := base64.StdEncoding.DecodeString(envVarValue)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read base64 value %s (defined by env var %s): %s", envVarValue, envVar, err)
|
||||
}
|
||||
return bytes, nil
|
||||
}
|
||||
|
||||
fileVar := envVar + "_FILE"
|
||||
fileVarValue := os.Getenv(fileVar)
|
||||
if fileVarValue == "" {
|
||||
return nil, fmt.Errorf("no value provided for: %s or %s", envVar, fileVar)
|
||||
}
|
||||
|
||||
fileContents, err := ioutil.ReadFile(fileVarValue)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read the file %s (defined by env var %s): %s", fileVarValue, fileVar, err)
|
||||
}
|
||||
|
||||
return fileContents, nil
|
||||
}
|
175
vendor/github.com/go-acme/lego/providers/dns/oraclecloud/oraclecloud.go
generated
vendored
Normal file
175
vendor/github.com/go-acme/lego/providers/dns/oraclecloud/oraclecloud.go
generated
vendored
Normal file
|
@ -0,0 +1,175 @@
|
|||
package oraclecloud
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
"github.com/oracle/oci-go-sdk/common"
|
||||
"github.com/oracle/oci-go-sdk/dns"
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
CompartmentID string
|
||||
OCIConfigProvider common.ConfigurationProvider
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt("OCI_TTL", dns01.DefaultTTL),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("OCI_PROPAGATION_TIMEOUT", dns01.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("OCI_POLLING_INTERVAL", dns01.DefaultPollingInterval),
|
||||
HTTPClient: &http.Client{
|
||||
Timeout: env.GetOrDefaultSecond("OCI_HTTP_TIMEOUT", 60*time.Second),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface.
|
||||
type DNSProvider struct {
|
||||
client *dns.DnsClient
|
||||
config *Config
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for OracleCloud.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get(ociPrivkey, ociTenancyOCID, ociUserOCID, ociPubkeyFingerprint, ociRegion, "OCI_COMPARTMENT_OCID")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("oraclecloud: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.CompartmentID = values["OCI_COMPARTMENT_OCID"]
|
||||
config.OCIConfigProvider = newConfigProvider(values)
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for OracleCloud.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("oraclecloud: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if config.CompartmentID == "" {
|
||||
return nil, errors.New("oraclecloud: CompartmentID is missing")
|
||||
}
|
||||
|
||||
if config.OCIConfigProvider == nil {
|
||||
return nil, errors.New("oraclecloud: OCIConfigProvider is missing")
|
||||
}
|
||||
|
||||
client, err := dns.NewDnsClientWithConfigurationProvider(config.OCIConfigProvider)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("oraclecloud: %v", err)
|
||||
}
|
||||
|
||||
if config.HTTPClient != nil {
|
||||
client.HTTPClient = config.HTTPClient
|
||||
}
|
||||
|
||||
return &DNSProvider{client: &client, config: config}, nil
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
// generate request to dns.PatchDomainRecordsRequest
|
||||
recordOperation := dns.RecordOperation{
|
||||
Domain: common.String(dns01.UnFqdn(fqdn)),
|
||||
Rdata: common.String(value),
|
||||
Rtype: common.String("TXT"),
|
||||
Ttl: common.Int(d.config.TTL),
|
||||
IsProtected: common.Bool(false),
|
||||
}
|
||||
|
||||
request := dns.PatchDomainRecordsRequest{
|
||||
CompartmentId: common.String(d.config.CompartmentID),
|
||||
ZoneNameOrId: common.String(domain),
|
||||
Domain: common.String(dns01.UnFqdn(fqdn)),
|
||||
PatchDomainRecordsDetails: dns.PatchDomainRecordsDetails{
|
||||
Items: []dns.RecordOperation{recordOperation},
|
||||
},
|
||||
}
|
||||
|
||||
_, err := d.client.PatchDomainRecords(context.Background(), request)
|
||||
if err != nil {
|
||||
return fmt.Errorf("oraclecloud: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
// search to TXT record's hash to delete
|
||||
getRequest := dns.GetDomainRecordsRequest{
|
||||
ZoneNameOrId: common.String(domain),
|
||||
Domain: common.String(dns01.UnFqdn(fqdn)),
|
||||
CompartmentId: common.String(d.config.CompartmentID),
|
||||
Rtype: common.String("TXT"),
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
domainRecords, err := d.client.GetDomainRecords(ctx, getRequest)
|
||||
if err != nil {
|
||||
return fmt.Errorf("oraclecloud: %v", err)
|
||||
}
|
||||
|
||||
if *domainRecords.OpcTotalItems == 0 {
|
||||
return fmt.Errorf("oraclecloud: no record to CleanUp")
|
||||
}
|
||||
|
||||
var deleteHash *string
|
||||
for _, record := range domainRecords.RecordCollection.Items {
|
||||
if record.Rdata != nil && *record.Rdata == `"`+value+`"` {
|
||||
deleteHash = record.RecordHash
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if deleteHash == nil {
|
||||
return fmt.Errorf("oraclecloud: no record to CleanUp")
|
||||
}
|
||||
|
||||
recordOperation := dns.RecordOperation{
|
||||
RecordHash: deleteHash,
|
||||
Operation: dns.RecordOperationOperationRemove,
|
||||
}
|
||||
|
||||
patchRequest := dns.PatchDomainRecordsRequest{
|
||||
ZoneNameOrId: common.String(domain),
|
||||
Domain: common.String(dns01.UnFqdn(fqdn)),
|
||||
PatchDomainRecordsDetails: dns.PatchDomainRecordsDetails{
|
||||
Items: []dns.RecordOperation{recordOperation},
|
||||
},
|
||||
CompartmentId: common.String(d.config.CompartmentID),
|
||||
}
|
||||
|
||||
_, err = d.client.PatchDomainRecords(ctx, patchRequest)
|
||||
if err != nil {
|
||||
return fmt.Errorf("oraclecloud: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Timeout returns the timeout and interval to use when checking for DNS propagation.
|
||||
// Adjusting here to cope with spikes in propagation times.
|
||||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
263
vendor/github.com/go-acme/lego/providers/dns/otc/client.go
generated
vendored
Normal file
263
vendor/github.com/go-acme/lego/providers/dns/otc/client.go
generated
vendored
Normal file
|
@ -0,0 +1,263 @@
|
|||
package otc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type recordset struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Type string `json:"type"`
|
||||
TTL int `json:"ttl"`
|
||||
Records []string `json:"records"`
|
||||
}
|
||||
|
||||
type nameResponse struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type userResponse struct {
|
||||
Name string `json:"name"`
|
||||
Password string `json:"password"`
|
||||
Domain nameResponse `json:"domain"`
|
||||
}
|
||||
|
||||
type passwordResponse struct {
|
||||
User userResponse `json:"user"`
|
||||
}
|
||||
|
||||
type identityResponse struct {
|
||||
Methods []string `json:"methods"`
|
||||
Password passwordResponse `json:"password"`
|
||||
}
|
||||
|
||||
type scopeResponse struct {
|
||||
Project nameResponse `json:"project"`
|
||||
}
|
||||
|
||||
type authResponse struct {
|
||||
Identity identityResponse `json:"identity"`
|
||||
Scope scopeResponse `json:"scope"`
|
||||
}
|
||||
|
||||
type loginResponse struct {
|
||||
Auth authResponse `json:"auth"`
|
||||
}
|
||||
|
||||
type endpointResponse struct {
|
||||
Token token `json:"token"`
|
||||
}
|
||||
|
||||
type token struct {
|
||||
Catalog []catalog `json:"catalog"`
|
||||
}
|
||||
|
||||
type catalog struct {
|
||||
Type string `json:"type"`
|
||||
Endpoints []endpoint `json:"endpoints"`
|
||||
}
|
||||
|
||||
type endpoint struct {
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
type zoneItem struct {
|
||||
ID string `json:"id"`
|
||||
}
|
||||
|
||||
type zonesResponse struct {
|
||||
Zones []zoneItem `json:"zones"`
|
||||
}
|
||||
|
||||
type recordSet struct {
|
||||
ID string `json:"id"`
|
||||
}
|
||||
|
||||
type recordSetsResponse struct {
|
||||
RecordSets []recordSet `json:"recordsets"`
|
||||
}
|
||||
|
||||
// Starts a new OTC API Session. Authenticates using userName, password
|
||||
// and receives a token to be used in for subsequent requests.
|
||||
func (d *DNSProvider) login() error {
|
||||
return d.loginRequest()
|
||||
}
|
||||
|
||||
func (d *DNSProvider) loginRequest() error {
|
||||
userResp := userResponse{
|
||||
Name: d.config.UserName,
|
||||
Password: d.config.Password,
|
||||
Domain: nameResponse{
|
||||
Name: d.config.DomainName,
|
||||
},
|
||||
}
|
||||
|
||||
loginResp := loginResponse{
|
||||
Auth: authResponse{
|
||||
Identity: identityResponse{
|
||||
Methods: []string{"password"},
|
||||
Password: passwordResponse{
|
||||
User: userResp,
|
||||
},
|
||||
},
|
||||
Scope: scopeResponse{
|
||||
Project: nameResponse{
|
||||
Name: d.config.ProjectName,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
body, err := json.Marshal(loginResp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, d.config.IdentityEndpoint, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
client := &http.Client{Timeout: d.config.HTTPClient.Timeout}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode >= 400 {
|
||||
return fmt.Errorf("OTC API request failed with HTTP status code %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
d.token = resp.Header.Get("X-Subject-Token")
|
||||
|
||||
if d.token == "" {
|
||||
return fmt.Errorf("unable to get auth token")
|
||||
}
|
||||
|
||||
var endpointResp endpointResponse
|
||||
|
||||
err = json.NewDecoder(resp.Body).Decode(&endpointResp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var endpoints []endpoint
|
||||
for _, v := range endpointResp.Token.Catalog {
|
||||
if v.Type == "dns" {
|
||||
endpoints = append(endpoints, v.Endpoints...)
|
||||
}
|
||||
}
|
||||
|
||||
if len(endpoints) > 0 {
|
||||
d.baseURL = fmt.Sprintf("%s/v2", endpoints[0].URL)
|
||||
} else {
|
||||
return fmt.Errorf("unable to get dns endpoint")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) getZoneID(zone string) (string, error) {
|
||||
resource := fmt.Sprintf("zones?name=%s", zone)
|
||||
resp, err := d.sendRequest(http.MethodGet, resource, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var zonesRes zonesResponse
|
||||
err = json.NewDecoder(resp).Decode(&zonesRes)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(zonesRes.Zones) < 1 {
|
||||
return "", fmt.Errorf("zone %s not found", zone)
|
||||
}
|
||||
|
||||
if len(zonesRes.Zones) > 1 {
|
||||
return "", fmt.Errorf("to many zones found")
|
||||
}
|
||||
|
||||
if zonesRes.Zones[0].ID == "" {
|
||||
return "", fmt.Errorf("id not found")
|
||||
}
|
||||
|
||||
return zonesRes.Zones[0].ID, nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) getRecordSetID(zoneID string, fqdn string) (string, error) {
|
||||
resource := fmt.Sprintf("zones/%s/recordsets?type=TXT&name=%s", zoneID, fqdn)
|
||||
resp, err := d.sendRequest(http.MethodGet, resource, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var recordSetsRes recordSetsResponse
|
||||
err = json.NewDecoder(resp).Decode(&recordSetsRes)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(recordSetsRes.RecordSets) < 1 {
|
||||
return "", fmt.Errorf("record not found")
|
||||
}
|
||||
|
||||
if len(recordSetsRes.RecordSets) > 1 {
|
||||
return "", fmt.Errorf("to many records found")
|
||||
}
|
||||
|
||||
if recordSetsRes.RecordSets[0].ID == "" {
|
||||
return "", fmt.Errorf("id not found")
|
||||
}
|
||||
|
||||
return recordSetsRes.RecordSets[0].ID, nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) deleteRecordSet(zoneID, recordID string) error {
|
||||
resource := fmt.Sprintf("zones/%s/recordsets/%s", zoneID, recordID)
|
||||
|
||||
_, err := d.sendRequest(http.MethodDelete, resource, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *DNSProvider) sendRequest(method, resource string, payload interface{}) (io.Reader, error) {
|
||||
url := fmt.Sprintf("%s/%s", d.baseURL, resource)
|
||||
|
||||
body, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, url, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
if len(d.token) > 0 {
|
||||
req.Header.Set("X-Auth-Token", d.token)
|
||||
}
|
||||
|
||||
resp, err := d.config.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode >= 400 {
|
||||
return nil, fmt.Errorf("OTC API request %s failed with HTTP status code %d", url, resp.StatusCode)
|
||||
}
|
||||
|
||||
body1, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return bytes.NewReader(body1), nil
|
||||
}
|
179
vendor/github.com/go-acme/lego/providers/dns/otc/otc.go
generated
vendored
Normal file
179
vendor/github.com/go-acme/lego/providers/dns/otc/otc.go
generated
vendored
Normal file
|
@ -0,0 +1,179 @@
|
|||
// Package otc implements a DNS provider for solving the DNS-01 challenge using Open Telekom Cloud Managed DNS.
|
||||
package otc
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
)
|
||||
|
||||
const defaultIdentityEndpoint = "https://iam.eu-de.otc.t-systems.com:443/v3/auth/tokens"
|
||||
|
||||
// minTTL 300 is otc minimum value for ttl
|
||||
const minTTL = 300
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
IdentityEndpoint string
|
||||
DomainName string
|
||||
ProjectName string
|
||||
UserName string
|
||||
Password string
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
IdentityEndpoint: env.GetOrDefaultString("OTC_IDENTITY_ENDPOINT", defaultIdentityEndpoint),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("OTC_PROPAGATION_TIMEOUT", dns01.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("OTC_POLLING_INTERVAL", dns01.DefaultPollingInterval),
|
||||
TTL: env.GetOrDefaultInt("OTC_TTL", minTTL),
|
||||
HTTPClient: &http.Client{
|
||||
Timeout: env.GetOrDefaultSecond("OTC_HTTP_TIMEOUT", 10*time.Second),
|
||||
Transport: &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
DialContext: (&net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
DualStack: true,
|
||||
}).DialContext,
|
||||
MaxIdleConns: 100,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
|
||||
// Workaround for keep alive bug in otc api
|
||||
DisableKeepAlives: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface that uses
|
||||
// OTC's Managed DNS API to manage TXT records for a domain.
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
baseURL string
|
||||
token string
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for OTC DNS.
|
||||
// Credentials must be passed in the environment variables: OTC_USER_NAME,
|
||||
// OTC_DOMAIN_NAME, OTC_PASSWORD OTC_PROJECT_NAME and OTC_IDENTITY_ENDPOINT.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("OTC_DOMAIN_NAME", "OTC_USER_NAME", "OTC_PASSWORD", "OTC_PROJECT_NAME")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("otc: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.DomainName = values["OTC_DOMAIN_NAME"]
|
||||
config.UserName = values["OTC_USER_NAME"]
|
||||
config.Password = values["OTC_PASSWORD"]
|
||||
config.ProjectName = values["OTC_PROJECT_NAME"]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for OTC DNS.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("otc: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if config.DomainName == "" || config.UserName == "" || config.Password == "" || config.ProjectName == "" {
|
||||
return nil, fmt.Errorf("otc: credentials missing")
|
||||
}
|
||||
|
||||
if config.TTL < minTTL {
|
||||
return nil, fmt.Errorf("otc: invalid TTL, TTL (%d) must be greater than %d", config.TTL, minTTL)
|
||||
}
|
||||
|
||||
if config.IdentityEndpoint == "" {
|
||||
config.IdentityEndpoint = defaultIdentityEndpoint
|
||||
}
|
||||
|
||||
return &DNSProvider{config: config}, nil
|
||||
}
|
||||
|
||||
// Present creates a TXT record using the specified parameters
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
authZone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("otc: %v", err)
|
||||
}
|
||||
|
||||
err = d.login()
|
||||
if err != nil {
|
||||
return fmt.Errorf("otc: %v", err)
|
||||
}
|
||||
|
||||
zoneID, err := d.getZoneID(authZone)
|
||||
if err != nil {
|
||||
return fmt.Errorf("otc: unable to get zone: %s", err)
|
||||
}
|
||||
|
||||
resource := fmt.Sprintf("zones/%s/recordsets", zoneID)
|
||||
|
||||
r1 := &recordset{
|
||||
Name: fqdn,
|
||||
Description: "Added TXT record for ACME dns-01 challenge using lego client",
|
||||
Type: "TXT",
|
||||
TTL: d.config.TTL,
|
||||
Records: []string{fmt.Sprintf("\"%s\"", value)},
|
||||
}
|
||||
|
||||
_, err = d.sendRequest(http.MethodPost, resource, r1)
|
||||
if err != nil {
|
||||
return fmt.Errorf("otc: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
authZone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("otc: %v", err)
|
||||
}
|
||||
|
||||
err = d.login()
|
||||
if err != nil {
|
||||
return fmt.Errorf("otc: %v", err)
|
||||
}
|
||||
|
||||
zoneID, err := d.getZoneID(authZone)
|
||||
if err != nil {
|
||||
return fmt.Errorf("otc: %v", err)
|
||||
}
|
||||
|
||||
recordID, err := d.getRecordSetID(zoneID, fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("otc: unable go get record %s for zone %s: %s", fqdn, domain, err)
|
||||
}
|
||||
|
||||
err = d.deleteRecordSet(zoneID, recordID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("otc: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Timeout returns the timeout and interval to use when checking for DNS propagation.
|
||||
// Adjusting here to cope with spikes in propagation times.
|
||||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
203
vendor/github.com/go-acme/lego/providers/dns/ovh/ovh.go
generated
vendored
Normal file
203
vendor/github.com/go-acme/lego/providers/dns/ovh/ovh.go
generated
vendored
Normal file
|
@ -0,0 +1,203 @@
|
|||
// Package ovh implements a DNS provider for solving the DNS-01 challenge using OVH DNS.
|
||||
package ovh
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
"github.com/ovh/go-ovh/ovh"
|
||||
)
|
||||
|
||||
// OVH API reference: https://eu.api.ovh.com/
|
||||
// Create a Token: https://eu.api.ovh.com/createToken/
|
||||
|
||||
// Record a DNS record
|
||||
type Record struct {
|
||||
ID int `json:"id,omitempty"`
|
||||
FieldType string `json:"fieldType,omitempty"`
|
||||
SubDomain string `json:"subDomain,omitempty"`
|
||||
Target string `json:"target,omitempty"`
|
||||
TTL int `json:"ttl,omitempty"`
|
||||
Zone string `json:"zone,omitempty"`
|
||||
}
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
APIEndpoint string
|
||||
ApplicationKey string
|
||||
ApplicationSecret string
|
||||
ConsumerKey string
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt("OVH_TTL", dns01.DefaultTTL),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("OVH_PROPAGATION_TIMEOUT", dns01.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("OVH_POLLING_INTERVAL", dns01.DefaultPollingInterval),
|
||||
HTTPClient: &http.Client{
|
||||
Timeout: env.GetOrDefaultSecond("OVH_HTTP_TIMEOUT", ovh.DefaultTimeout),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
config *Config
|
||||
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) {
|
||||
values, err := env.Get("OVH_ENDPOINT", "OVH_APPLICATION_KEY", "OVH_APPLICATION_SECRET", "OVH_CONSUMER_KEY")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ovh: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.APIEndpoint = values["OVH_ENDPOINT"]
|
||||
config.ApplicationKey = values["OVH_APPLICATION_KEY"]
|
||||
config.ApplicationSecret = values["OVH_APPLICATION_SECRET"]
|
||||
config.ConsumerKey = values["OVH_CONSUMER_KEY"]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for OVH.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("ovh: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if config.APIEndpoint == "" || config.ApplicationKey == "" || config.ApplicationSecret == "" || config.ConsumerKey == "" {
|
||||
return nil, fmt.Errorf("ovh: credentials missing")
|
||||
}
|
||||
|
||||
client, err := ovh.NewClient(
|
||||
config.APIEndpoint,
|
||||
config.ApplicationKey,
|
||||
config.ApplicationSecret,
|
||||
config.ConsumerKey,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ovh: %v", err)
|
||||
}
|
||||
|
||||
client.Client = config.HTTPClient
|
||||
|
||||
return &DNSProvider{
|
||||
config: config,
|
||||
client: client,
|
||||
recordIDs: make(map[string]int),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge.
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
// Parse domain name
|
||||
authZone, err := dns01.FindZoneByFqdn(dns01.ToFqdn(domain))
|
||||
if err != nil {
|
||||
return fmt.Errorf("ovh: could not determine zone for domain: '%s'. %s", domain, err)
|
||||
}
|
||||
|
||||
authZone = dns01.UnFqdn(authZone)
|
||||
subDomain := d.extractRecordName(fqdn, authZone)
|
||||
|
||||
reqURL := fmt.Sprintf("/domain/zone/%s/record", authZone)
|
||||
reqData := Record{FieldType: "TXT", SubDomain: subDomain, Target: value, TTL: d.config.TTL}
|
||||
|
||||
// Create TXT record
|
||||
var respData Record
|
||||
err = d.client.Post(reqURL, reqData, &respData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ovh: error when call api to add record (%s): %v", reqURL, err)
|
||||
}
|
||||
|
||||
// Apply the change
|
||||
reqURL = fmt.Sprintf("/domain/zone/%s/refresh", authZone)
|
||||
err = d.client.Post(reqURL, nil, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ovh: error when call api to refresh zone (%s): %v", reqURL, 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, _ := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
// get the record's unique ID from when we created it
|
||||
d.recordIDsMu.Lock()
|
||||
recordID, ok := d.recordIDs[fqdn]
|
||||
d.recordIDsMu.Unlock()
|
||||
if !ok {
|
||||
return fmt.Errorf("ovh: unknown record ID for '%s'", fqdn)
|
||||
}
|
||||
|
||||
authZone, err := dns01.FindZoneByFqdn(dns01.ToFqdn(domain))
|
||||
if err != nil {
|
||||
return fmt.Errorf("ovh: could not determine zone for domain: '%s'. %s", domain, err)
|
||||
}
|
||||
|
||||
authZone = dns01.UnFqdn(authZone)
|
||||
|
||||
reqURL := fmt.Sprintf("/domain/zone/%s/record/%d", authZone, recordID)
|
||||
|
||||
err = d.client.Delete(reqURL, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ovh: error when call OVH api to delete challenge record (%s): %v", reqURL, err)
|
||||
}
|
||||
|
||||
// Apply the change
|
||||
reqURL = fmt.Sprintf("/domain/zone/%s/refresh", authZone)
|
||||
err = d.client.Post(reqURL, nil, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ovh: error when call api to refresh zone (%s): %v", reqURL, err)
|
||||
}
|
||||
|
||||
// Delete record ID from map
|
||||
d.recordIDsMu.Lock()
|
||||
delete(d.recordIDs, fqdn)
|
||||
d.recordIDsMu.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Timeout returns the timeout and interval to use when checking for DNS propagation.
|
||||
// Adjusting here to cope with spikes in propagation times.
|
||||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
func (d *DNSProvider) extractRecordName(fqdn, domain string) string {
|
||||
name := dns01.UnFqdn(fqdn)
|
||||
if idx := strings.Index(name, "."+domain); idx != -1 {
|
||||
return name[:idx]
|
||||
}
|
||||
return name
|
||||
}
|
220
vendor/github.com/go-acme/lego/providers/dns/pdns/client.go
generated
vendored
Normal file
220
vendor/github.com/go-acme/lego/providers/dns/pdns/client.go
generated
vendored
Normal file
|
@ -0,0 +1,220 @@
|
|||
package pdns
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
)
|
||||
|
||||
type Record 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 []Record `json:"records"`
|
||||
}
|
||||
|
||||
type rrSet struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Kind string `json:"kind"`
|
||||
ChangeType string `json:"changetype"`
|
||||
Records []Record `json:"records"`
|
||||
TTL int `json:"ttl,omitempty"`
|
||||
}
|
||||
|
||||
type rrSets struct {
|
||||
RRSets []rrSet `json:"rrsets"`
|
||||
}
|
||||
|
||||
type apiError struct {
|
||||
ShortMsg string `json:"error"`
|
||||
}
|
||||
|
||||
func (a apiError) Error() string {
|
||||
return a.ShortMsg
|
||||
}
|
||||
|
||||
type apiVersion struct {
|
||||
URL string `json:"url"`
|
||||
Version int `json:"version"`
|
||||
}
|
||||
|
||||
func (d *DNSProvider) getHostedZone(fqdn string) (*hostedZone, error) {
|
||||
var zone hostedZone
|
||||
authZone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
u := "/servers/localhost/zones"
|
||||
result, err := d.sendRequest(http.MethodGet, u, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var zones []hostedZone
|
||||
err = json.Unmarshal(result, &zones)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
u = ""
|
||||
for _, zone := range zones {
|
||||
if dns01.UnFqdn(zone.Name) == dns01.UnFqdn(authZone) {
|
||||
u = zone.URL
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
result, err = d.sendRequest(http.MethodGet, u, 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: []Record{record},
|
||||
}
|
||||
zone.RRSets = append(zone.RRSets, set)
|
||||
}
|
||||
}
|
||||
|
||||
return &zone, nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) findTxtRecord(fqdn string) (*rrSet, error) {
|
||||
zone, err := d.getHostedZone(fqdn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = d.sendRequest(http.MethodGet, zone.URL, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, set := range zone.RRSets {
|
||||
if (set.Name == dns01.UnFqdn(fqdn) || set.Name == fqdn) && set.Type == "TXT" {
|
||||
return &set, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("no existing record found for %s", fqdn)
|
||||
}
|
||||
|
||||
func (d *DNSProvider) getAPIVersion() (int, error) {
|
||||
result, err := d.sendRequest(http.MethodGet, "/api", nil)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var versions []apiVersion
|
||||
err = json.Unmarshal(result, &versions)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
latestVersion := 0
|
||||
for _, v := range versions {
|
||||
if v.Version > latestVersion {
|
||||
latestVersion = v.Version
|
||||
}
|
||||
}
|
||||
|
||||
return latestVersion, err
|
||||
}
|
||||
|
||||
func (d *DNSProvider) sendRequest(method, uri string, body io.Reader) (json.RawMessage, error) {
|
||||
req, err := d.makeRequest(method, uri, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := d.config.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error talking to PDNS API -> %v", err)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusUnprocessableEntity && (resp.StatusCode < 200 || resp.StatusCode >= 300) {
|
||||
return nil, fmt.Errorf("unexpected HTTP status code %d when fetching '%s'", resp.StatusCode, req.URL)
|
||||
}
|
||||
|
||||
var msg json.RawMessage
|
||||
err = json.NewDecoder(resp.Body).Decode(&msg)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
// empty body
|
||||
return nil, nil
|
||||
}
|
||||
// other error
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// check for PowerDNS error message
|
||||
if len(msg) > 0 && msg[0] == '{' {
|
||||
var errInfo apiError
|
||||
err = json.Unmarshal(msg, &errInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if errInfo.ShortMsg != "" {
|
||||
return nil, fmt.Errorf("error talking to PDNS API -> %v", errInfo)
|
||||
}
|
||||
}
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) makeRequest(method, uri string, body io.Reader) (*http.Request, error) {
|
||||
var path = ""
|
||||
if d.config.Host.Path != "/" {
|
||||
path = d.config.Host.Path
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(uri, "/") {
|
||||
uri = "/" + uri
|
||||
}
|
||||
|
||||
if d.apiVersion > 0 && !strings.HasPrefix(uri, "/api/v") {
|
||||
uri = "/api/v" + strconv.Itoa(d.apiVersion) + uri
|
||||
}
|
||||
|
||||
u := d.config.Host.Scheme + "://" + d.config.Host.Host + path + uri
|
||||
req, err := http.NewRequest(method, u, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("X-API-Key", d.config.APIKey)
|
||||
|
||||
return req, nil
|
||||
}
|
182
vendor/github.com/go-acme/lego/providers/dns/pdns/pdns.go
generated
vendored
Normal file
182
vendor/github.com/go-acme/lego/providers/dns/pdns/pdns.go
generated
vendored
Normal file
|
@ -0,0 +1,182 @@
|
|||
// Package pdns implements a DNS provider for solving the DNS-01 challenge using PowerDNS nameserver.
|
||||
package pdns
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/log"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
APIKey string
|
||||
Host *url.URL
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt("PDNS_TTL", dns01.DefaultTTL),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("PDNS_PROPAGATION_TIMEOUT", 120*time.Second),
|
||||
PollingInterval: env.GetOrDefaultSecond("PDNS_POLLING_INTERVAL", 2*time.Second),
|
||||
HTTPClient: &http.Client{
|
||||
Timeout: env.GetOrDefaultSecond("PDNS_HTTP_TIMEOUT", 30*time.Second),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface
|
||||
type DNSProvider struct {
|
||||
apiVersion int
|
||||
config *Config
|
||||
}
|
||||
|
||||
// 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) {
|
||||
values, err := env.Get("PDNS_API_KEY", "PDNS_API_URL")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("pdns: %v", err)
|
||||
}
|
||||
|
||||
hostURL, err := url.Parse(values["PDNS_API_URL"])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("pdns: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.Host = hostURL
|
||||
config.APIKey = values["PDNS_API_KEY"]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for pdns.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("pdns: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if config.APIKey == "" {
|
||||
return nil, fmt.Errorf("pdns: API key missing")
|
||||
}
|
||||
|
||||
if config.Host == nil || config.Host.Host == "" {
|
||||
return nil, fmt.Errorf("pdns: API URL missing")
|
||||
}
|
||||
|
||||
d := &DNSProvider{config: config}
|
||||
|
||||
apiVersion, err := d.getAPIVersion()
|
||||
if err != nil {
|
||||
log.Warnf("pdns: failed to get API version %v", err)
|
||||
}
|
||||
d.apiVersion = apiVersion
|
||||
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// Timeout returns the timeout and interval to use when checking for DNS
|
||||
// propagation. Adjusting here to cope with spikes in propagation times.
|
||||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
zone, err := d.getHostedZone(fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("pdns: %v", err)
|
||||
}
|
||||
|
||||
name := fqdn
|
||||
|
||||
// pre-v1 API wants non-fqdn
|
||||
if d.apiVersion == 0 {
|
||||
name = dns01.UnFqdn(fqdn)
|
||||
}
|
||||
|
||||
rec := Record{
|
||||
Content: "\"" + value + "\"",
|
||||
Disabled: false,
|
||||
|
||||
// pre-v1 API
|
||||
Type: "TXT",
|
||||
Name: name,
|
||||
TTL: d.config.TTL,
|
||||
}
|
||||
|
||||
rrsets := rrSets{
|
||||
RRSets: []rrSet{
|
||||
{
|
||||
Name: name,
|
||||
ChangeType: "REPLACE",
|
||||
Type: "TXT",
|
||||
Kind: "Master",
|
||||
TTL: d.config.TTL,
|
||||
Records: []Record{rec},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
body, err := json.Marshal(rrsets)
|
||||
if err != nil {
|
||||
return fmt.Errorf("pdns: %v", err)
|
||||
}
|
||||
|
||||
_, err = d.sendRequest(http.MethodPatch, zone.URL, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return fmt.Errorf("pdns: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
zone, err := d.getHostedZone(fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("pdns: %v", err)
|
||||
}
|
||||
|
||||
set, err := d.findTxtRecord(fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("pdns: %v", err)
|
||||
}
|
||||
|
||||
rrsets := rrSets{
|
||||
RRSets: []rrSet{
|
||||
{
|
||||
Name: set.Name,
|
||||
Type: set.Type,
|
||||
ChangeType: "DELETE",
|
||||
},
|
||||
},
|
||||
}
|
||||
body, err := json.Marshal(rrsets)
|
||||
if err != nil {
|
||||
return fmt.Errorf("pdns: %v", err)
|
||||
}
|
||||
|
||||
_, err = d.sendRequest(http.MethodPatch, zone.URL, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return fmt.Errorf("pdns: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
205
vendor/github.com/go-acme/lego/providers/dns/rackspace/client.go
generated
vendored
Normal file
205
vendor/github.com/go-acme/lego/providers/dns/rackspace/client.go
generated
vendored
Normal file
|
@ -0,0 +1,205 @@
|
|||
package rackspace
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
)
|
||||
|
||||
// APIKeyCredentials API credential
|
||||
type APIKeyCredentials struct {
|
||||
Username string `json:"username"`
|
||||
APIKey string `json:"apiKey"`
|
||||
}
|
||||
|
||||
// Auth auth credentials
|
||||
type Auth struct {
|
||||
APIKeyCredentials `json:"RAX-KSKEY:apiKeyCredentials"`
|
||||
}
|
||||
|
||||
// AuthData Auth data
|
||||
type AuthData struct {
|
||||
Auth `json:"auth"`
|
||||
}
|
||||
|
||||
// Identity Identity
|
||||
type Identity struct {
|
||||
Access Access `json:"access"`
|
||||
}
|
||||
|
||||
// Access Access
|
||||
type Access struct {
|
||||
ServiceCatalog []ServiceCatalog `json:"serviceCatalog"`
|
||||
Token Token `json:"token"`
|
||||
}
|
||||
|
||||
// Token Token
|
||||
type Token struct {
|
||||
ID string `json:"id"`
|
||||
}
|
||||
|
||||
// ServiceCatalog ServiceCatalog
|
||||
type ServiceCatalog struct {
|
||||
Endpoints []Endpoint `json:"endpoints"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// Endpoint Endpoint
|
||||
type Endpoint struct {
|
||||
PublicURL string `json:"publicURL"`
|
||||
TenantID string `json:"tenantId"`
|
||||
}
|
||||
|
||||
// ZoneSearchResponse represents the response when querying Rackspace DNS zones
|
||||
type ZoneSearchResponse struct {
|
||||
TotalEntries int `json:"totalEntries"`
|
||||
HostedZones []HostedZone `json:"domains"`
|
||||
}
|
||||
|
||||
// HostedZone HostedZone
|
||||
type HostedZone struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// Records is the list of records sent/received from the DNS API
|
||||
type Records struct {
|
||||
Record []Record `json:"records"`
|
||||
}
|
||||
|
||||
// Record represents a Rackspace DNS record
|
||||
type Record struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Data string `json:"data"`
|
||||
TTL int `json:"ttl,omitempty"`
|
||||
ID string `json:"id,omitempty"`
|
||||
}
|
||||
|
||||
// getHostedZoneID performs a lookup to get the DNS zone which needs
|
||||
// modifying for a given FQDN
|
||||
func (d *DNSProvider) getHostedZoneID(fqdn string) (int, error) {
|
||||
authZone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
result, err := d.makeRequest(http.MethodGet, fmt.Sprintf("/domains?name=%s", dns01.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 (d *DNSProvider) findTxtRecord(fqdn string, zoneID int) (*Record, error) {
|
||||
result, err := d.makeRequest(http.MethodGet, fmt.Sprintf("/domains/%d/records?type=TXT&name=%s", zoneID, dns01.UnFqdn(fqdn)), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var records Records
|
||||
err = json.Unmarshal(result, &records)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch len(records.Record) {
|
||||
case 1:
|
||||
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.Record[0], nil
|
||||
}
|
||||
|
||||
// makeRequest is a wrapper function used for making DNS API requests
|
||||
func (d *DNSProvider) makeRequest(method, uri string, body io.Reader) (json.RawMessage, error) {
|
||||
url := d.cloudDNSEndpoint + uri
|
||||
|
||||
req, err := http.NewRequest(method, url, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("X-Auth-Token", d.token)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := d.config.HTTPClient.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
|
||||
}
|
||||
|
||||
func login(config *Config) (*Identity, error) {
|
||||
authData := AuthData{
|
||||
Auth: Auth{
|
||||
APIKeyCredentials: APIKeyCredentials{
|
||||
Username: config.APIUser,
|
||||
APIKey: config.APIKey,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
body, err := json.Marshal(authData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, config.BaseURL, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := config.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error querying Identity API: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("authentication failed: response code: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var identity Identity
|
||||
err = json.NewDecoder(resp.Body).Decode(&identity)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &identity, nil
|
||||
}
|
159
vendor/github.com/go-acme/lego/providers/dns/rackspace/rackspace.go
generated
vendored
Normal file
159
vendor/github.com/go-acme/lego/providers/dns/rackspace/rackspace.go
generated
vendored
Normal file
|
@ -0,0 +1,159 @@
|
|||
// Package rackspace implements a DNS provider for solving the DNS-01 challenge using rackspace DNS.
|
||||
package rackspace
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
)
|
||||
|
||||
// defaultBaseURL represents the Identity API endpoint to call
|
||||
const defaultBaseURL = "https://identity.api.rackspacecloud.com/v2.0/tokens"
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
BaseURL string
|
||||
APIUser string
|
||||
APIKey string
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
BaseURL: defaultBaseURL,
|
||||
TTL: env.GetOrDefaultInt("RACKSPACE_TTL", 300),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("RACKSPACE_PROPAGATION_TIMEOUT", dns01.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("RACKSPACE_POLLING_INTERVAL", dns01.DefaultPollingInterval),
|
||||
HTTPClient: &http.Client{
|
||||
Timeout: env.GetOrDefaultSecond("RACKSPACE_HTTP_TIMEOUT", 30*time.Second),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface
|
||||
// used to store the reusable token and DNS API endpoint
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
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) {
|
||||
values, err := env.Get("RACKSPACE_USER", "RACKSPACE_API_KEY")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("rackspace: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.APIUser = values["RACKSPACE_USER"]
|
||||
config.APIKey = values["RACKSPACE_API_KEY"]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for Rackspace.
|
||||
// It authenticates against the API, also grabbing the DNS Endpoint.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("rackspace: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if config.APIUser == "" || config.APIKey == "" {
|
||||
return nil, fmt.Errorf("rackspace: credentials missing")
|
||||
}
|
||||
|
||||
identity, err := login(config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("rackspace: %v", err)
|
||||
}
|
||||
|
||||
// Iterate through the Service Catalog to get the DNS Endpoint
|
||||
var dnsEndpoint string
|
||||
for _, service := range identity.Access.ServiceCatalog {
|
||||
if service.Name == "cloudDNS" {
|
||||
dnsEndpoint = service.Endpoints[0].PublicURL
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if dnsEndpoint == "" {
|
||||
return nil, fmt.Errorf("rackspace: failed to populate DNS endpoint, check Rackspace API for changes")
|
||||
}
|
||||
|
||||
return &DNSProvider{
|
||||
config: config,
|
||||
token: identity.Access.Token.ID,
|
||||
cloudDNSEndpoint: dnsEndpoint,
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
zoneID, err := d.getHostedZoneID(fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("rackspace: %v", err)
|
||||
}
|
||||
|
||||
rec := Records{
|
||||
Record: []Record{{
|
||||
Name: dns01.UnFqdn(fqdn),
|
||||
Type: "TXT",
|
||||
Data: value,
|
||||
TTL: d.config.TTL,
|
||||
}},
|
||||
}
|
||||
|
||||
body, err := json.Marshal(rec)
|
||||
if err != nil {
|
||||
return fmt.Errorf("rackspace: %v", err)
|
||||
}
|
||||
|
||||
_, err = d.makeRequest(http.MethodPost, fmt.Sprintf("/domains/%d/records", zoneID), bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return fmt.Errorf("rackspace: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
zoneID, err := d.getHostedZoneID(fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("rackspace: %v", err)
|
||||
}
|
||||
|
||||
record, err := d.findTxtRecord(fqdn, zoneID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("rackspace: %v", err)
|
||||
}
|
||||
|
||||
_, err = d.makeRequest(http.MethodDelete, fmt.Sprintf("/domains/%d/records?id=%s", zoneID, record.ID), nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("rackspace: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Timeout returns the timeout and interval to use when checking for DNS propagation.
|
||||
// Adjusting here to cope with spikes in propagation times.
|
||||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
183
vendor/github.com/go-acme/lego/providers/dns/rfc2136/rfc2136.go
generated
vendored
Normal file
183
vendor/github.com/go-acme/lego/providers/dns/rfc2136/rfc2136.go
generated
vendored
Normal file
|
@ -0,0 +1,183 @@
|
|||
// Package rfc2136 implements a DNS provider for solving the DNS-01 challenge using the rfc2136 dynamic update.
|
||||
package rfc2136
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
Nameserver string
|
||||
TSIGAlgorithm string
|
||||
TSIGKey string
|
||||
TSIGSecret string
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
SequenceInterval time.Duration
|
||||
DNSTimeout time.Duration
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TSIGAlgorithm: env.GetOrDefaultString("RFC2136_TSIG_ALGORITHM", dns.HmacMD5),
|
||||
TTL: env.GetOrDefaultInt("RFC2136_TTL", dns01.DefaultTTL),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("RFC2136_PROPAGATION_TIMEOUT", env.GetOrDefaultSecond("RFC2136_TIMEOUT", 60*time.Second)),
|
||||
PollingInterval: env.GetOrDefaultSecond("RFC2136_POLLING_INTERVAL", 2*time.Second),
|
||||
SequenceInterval: env.GetOrDefaultSecond("RFC2136_SEQUENCE_INTERVAL", dns01.DefaultPropagationTimeout),
|
||||
DNSTimeout: env.GetOrDefaultSecond("RFC2136_DNS_TIMEOUT", 10*time.Second),
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
config *Config
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for rfc2136
|
||||
// dynamic update. Configured with environment variables:
|
||||
// RFC2136_NAMESERVER: Network address in the form "host" or "host:port".
|
||||
// RFC2136_TSIG_ALGORITHM: Defaults to hmac-md5.sig-alg.reg.int. (HMAC-MD5).
|
||||
// See https://github.com/miekg/dns/blob/master/tsig.go for supported values.
|
||||
// RFC2136_TSIG_KEY: Name of the secret key as defined in DNS server configuration.
|
||||
// RFC2136_TSIG_SECRET: Secret key payload.
|
||||
// RFC2136_PROPAGATION_TIMEOUT: DNS propagation timeout in time.ParseDuration format. (60s)
|
||||
// To disable TSIG authentication, leave the RFC2136_TSIG* variables unset.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("RFC2136_NAMESERVER")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("rfc2136: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.Nameserver = values["RFC2136_NAMESERVER"]
|
||||
config.TSIGKey = env.GetOrFile("RFC2136_TSIG_KEY")
|
||||
config.TSIGSecret = env.GetOrFile("RFC2136_TSIG_SECRET")
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for rfc2136.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("rfc2136: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if config.Nameserver == "" {
|
||||
return nil, fmt.Errorf("rfc2136: nameserver missing")
|
||||
}
|
||||
|
||||
if config.TSIGAlgorithm == "" {
|
||||
config.TSIGAlgorithm = dns.HmacMD5
|
||||
}
|
||||
|
||||
// Append the default DNS port if none is specified.
|
||||
if _, _, err := net.SplitHostPort(config.Nameserver); err != nil {
|
||||
if strings.Contains(err.Error(), "missing port") {
|
||||
config.Nameserver = net.JoinHostPort(config.Nameserver, "53")
|
||||
} else {
|
||||
return nil, fmt.Errorf("rfc2136: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if len(config.TSIGKey) == 0 && len(config.TSIGSecret) > 0 ||
|
||||
len(config.TSIGKey) > 0 && len(config.TSIGSecret) == 0 {
|
||||
config.TSIGKey = ""
|
||||
config.TSIGSecret = ""
|
||||
}
|
||||
|
||||
return &DNSProvider{config: config}, nil
|
||||
}
|
||||
|
||||
// Timeout returns the timeout and interval to use when checking for DNS propagation.
|
||||
// Adjusting here to cope with spikes in propagation times.
|
||||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
// Sequential All DNS challenges for this provider will be resolved sequentially.
|
||||
// Returns the interval between each iteration.
|
||||
func (d *DNSProvider) Sequential() time.Duration {
|
||||
return d.config.SequenceInterval
|
||||
}
|
||||
|
||||
// Present creates a TXT record using the specified parameters
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
err := d.changeRecord("INSERT", fqdn, value, d.config.TTL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("rfc2136: failed to insert: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
err := d.changeRecord("REMOVE", fqdn, value, d.config.TTL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("rfc2136: failed to remove: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) changeRecord(action, fqdn, value string, ttl int) error {
|
||||
// Find the zone for the given fqdn
|
||||
zone, err := dns01.FindZoneByFqdnCustom(fqdn, []string{d.config.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 := &dns.Client{Timeout: d.config.DNSTimeout}
|
||||
c.SingleInflight = true
|
||||
|
||||
// TSIG authentication / msg signing
|
||||
if len(d.config.TSIGKey) > 0 && len(d.config.TSIGSecret) > 0 {
|
||||
m.SetTsig(dns.Fqdn(d.config.TSIGKey), d.config.TSIGAlgorithm, 300, time.Now().Unix())
|
||||
c.TsigSecret = map[string]string{dns.Fqdn(d.config.TSIGKey): d.config.TSIGSecret}
|
||||
}
|
||||
|
||||
// Send the query
|
||||
reply, _, err := c.Exchange(m, d.config.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
|
||||
}
|
279
vendor/github.com/go-acme/lego/providers/dns/route53/route53.go
generated
vendored
Normal file
279
vendor/github.com/go-acme/lego/providers/dns/route53/route53.go
generated
vendored
Normal file
|
@ -0,0 +1,279 @@
|
|||
// Package route53 implements a DNS provider for solving the DNS-01 challenge using AWS Route 53 DNS.
|
||||
package route53
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"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/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
"github.com/go-acme/lego/platform/wait"
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
MaxRetries int
|
||||
TTL int
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
HostedZoneID string
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
MaxRetries: env.GetOrDefaultInt("AWS_MAX_RETRIES", 5),
|
||||
TTL: env.GetOrDefaultInt("AWS_TTL", 10),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("AWS_PROPAGATION_TIMEOUT", 2*time.Minute),
|
||||
PollingInterval: env.GetOrDefaultSecond("AWS_POLLING_INTERVAL", 4*time.Second),
|
||||
HostedZoneID: env.GetOrFile("AWS_HOSTED_ZONE_ID"),
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider implements the acme.ChallengeProvider interface
|
||||
type DNSProvider struct {
|
||||
client *route53.Route53
|
||||
config *Config
|
||||
}
|
||||
|
||||
// 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
|
||||
//
|
||||
// If AWS_HOSTED_ZONE_ID is not set, Lego tries to determine the correct public hosted zone via the FQDN.
|
||||
//
|
||||
// See also: https://github.com/aws/aws-sdk-go/wiki/configuring-sdk
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
return NewDNSProviderConfig(NewDefaultConfig())
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig takes a given config ans returns a custom configured DNSProvider instance
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("route53: the configuration of the Route53 DNS provider is nil")
|
||||
}
|
||||
|
||||
retry := customRetryer{}
|
||||
retry.NumMaxRetries = config.MaxRetries
|
||||
sessionCfg := request.WithRetryer(aws.NewConfig(), retry)
|
||||
|
||||
sess, err := session.NewSessionWithOptions(session.Options{Config: *sessionCfg})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cl := route53.New(sess)
|
||||
return &DNSProvider{client: cl, config: config}, nil
|
||||
}
|
||||
|
||||
// Timeout returns the timeout and interval to use when checking for DNS
|
||||
// propagation.
|
||||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
// Present creates a TXT record using the specified parameters
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
hostedZoneID, err := d.getHostedZoneID(fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("route53: failed to determine hosted zone ID: %v", err)
|
||||
}
|
||||
|
||||
records, err := d.getExistingRecordSets(hostedZoneID, fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("route53: %v", err)
|
||||
}
|
||||
|
||||
realValue := `"` + value + `"`
|
||||
|
||||
var found bool
|
||||
for _, record := range records {
|
||||
if aws.StringValue(record.Value) == realValue {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
records = append(records, &route53.ResourceRecord{Value: aws.String(realValue)})
|
||||
}
|
||||
|
||||
recordSet := &route53.ResourceRecordSet{
|
||||
Name: aws.String(fqdn),
|
||||
Type: aws.String("TXT"),
|
||||
TTL: aws.Int64(int64(d.config.TTL)),
|
||||
ResourceRecords: records,
|
||||
}
|
||||
|
||||
err = d.changeRecord(route53.ChangeActionUpsert, hostedZoneID, recordSet)
|
||||
if err != nil {
|
||||
return fmt.Errorf("route53: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
hostedZoneID, err := d.getHostedZoneID(fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to determine Route 53 hosted zone ID: %v", err)
|
||||
}
|
||||
|
||||
records, err := d.getExistingRecordSets(hostedZoneID, fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("route53: %v", err)
|
||||
}
|
||||
|
||||
if len(records) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
recordSet := &route53.ResourceRecordSet{
|
||||
Name: aws.String(fqdn),
|
||||
Type: aws.String("TXT"),
|
||||
TTL: aws.Int64(int64(d.config.TTL)),
|
||||
ResourceRecords: records,
|
||||
}
|
||||
|
||||
err = d.changeRecord(route53.ChangeActionDelete, hostedZoneID, recordSet)
|
||||
if err != nil {
|
||||
return fmt.Errorf("route53: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) changeRecord(action, hostedZoneID string, recordSet *route53.ResourceRecordSet) error {
|
||||
recordSetInput := &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 := d.client.ChangeResourceRecordSets(recordSetInput)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to change record set: %v", err)
|
||||
}
|
||||
|
||||
changeID := resp.ChangeInfo.Id
|
||||
|
||||
return wait.For("route53", d.config.PropagationTimeout, d.config.PollingInterval, func() (bool, error) {
|
||||
reqParams := &route53.GetChangeInput{Id: changeID}
|
||||
|
||||
resp, err := d.client.GetChange(reqParams)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to query change status: %v", err)
|
||||
}
|
||||
|
||||
if aws.StringValue(resp.ChangeInfo.Status) == route53.ChangeStatusInsync {
|
||||
return true, nil
|
||||
}
|
||||
return false, fmt.Errorf("unable to retrieve change: ID=%s", aws.StringValue(changeID))
|
||||
})
|
||||
}
|
||||
|
||||
func (d *DNSProvider) getExistingRecordSets(hostedZoneID string, fqdn string) ([]*route53.ResourceRecord, error) {
|
||||
listInput := &route53.ListResourceRecordSetsInput{
|
||||
HostedZoneId: aws.String(hostedZoneID),
|
||||
StartRecordName: aws.String(fqdn),
|
||||
StartRecordType: aws.String("TXT"),
|
||||
}
|
||||
|
||||
recordSetsOutput, err := d.client.ListResourceRecordSets(listInput)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if recordSetsOutput == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var records []*route53.ResourceRecord
|
||||
|
||||
for _, recordSet := range recordSetsOutput.ResourceRecordSets {
|
||||
if aws.StringValue(recordSet.Name) == fqdn {
|
||||
records = append(records, recordSet.ResourceRecords...)
|
||||
}
|
||||
}
|
||||
|
||||
return records, nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) getHostedZoneID(fqdn string) (string, error) {
|
||||
if d.config.HostedZoneID != "" {
|
||||
return d.config.HostedZoneID, nil
|
||||
}
|
||||
|
||||
authZone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// .DNSName should not have a trailing dot
|
||||
reqParams := &route53.ListHostedZonesByNameInput{
|
||||
DNSName: aws.String(dns01.UnFqdn(authZone)),
|
||||
}
|
||||
resp, err := d.client.ListHostedZonesByName(reqParams)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var hostedZoneID string
|
||||
for _, hostedZone := range resp.HostedZones {
|
||||
// .Name has a trailing dot
|
||||
if !aws.BoolValue(hostedZone.Config.PrivateZone) && aws.StringValue(hostedZone.Name) == authZone {
|
||||
hostedZoneID = aws.StringValue(hostedZone.Id)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if len(hostedZoneID) == 0 {
|
||||
return "", fmt.Errorf("zone %s not found for domain %s", authZone, fqdn)
|
||||
}
|
||||
|
||||
if strings.HasPrefix(hostedZoneID, "/hostedzone/") {
|
||||
hostedZoneID = strings.TrimPrefix(hostedZoneID, "/hostedzone/")
|
||||
}
|
||||
|
||||
return hostedZoneID, nil
|
||||
}
|
173
vendor/github.com/go-acme/lego/providers/dns/sakuracloud/sakuracloud.go
generated
vendored
Normal file
173
vendor/github.com/go-acme/lego/providers/dns/sakuracloud/sakuracloud.go
generated
vendored
Normal file
|
@ -0,0 +1,173 @@
|
|||
// Package sakuracloud implements a DNS provider for solving the DNS-01 challenge using SakuraCloud DNS.
|
||||
package sakuracloud
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
"github.com/sacloud/libsacloud/api"
|
||||
"github.com/sacloud/libsacloud/sacloud"
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
Token string
|
||||
Secret string
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt("SAKURACLOUD_TTL", dns01.DefaultTTL),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("SAKURACLOUD_PROPAGATION_TIMEOUT", dns01.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("SAKURACLOUD_POLLING_INTERVAL", dns01.DefaultPollingInterval),
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface.
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
client *api.Client
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for SakuraCloud.
|
||||
// Credentials must be passed in the environment variables: SAKURACLOUD_ACCESS_TOKEN & SAKURACLOUD_ACCESS_TOKEN_SECRET
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("SAKURACLOUD_ACCESS_TOKEN", "SAKURACLOUD_ACCESS_TOKEN_SECRET")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("sakuracloud: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.Token = values["SAKURACLOUD_ACCESS_TOKEN"]
|
||||
config.Secret = values["SAKURACLOUD_ACCESS_TOKEN_SECRET"]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for SakuraCloud.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("sakuracloud: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if config.Token == "" {
|
||||
return nil, errors.New("sakuracloud: AccessToken is missing")
|
||||
}
|
||||
|
||||
if config.Secret == "" {
|
||||
return nil, errors.New("sakuracloud: AccessSecret is missing")
|
||||
}
|
||||
|
||||
client := api.NewClient(config.Token, config.Secret, "tk1a")
|
||||
|
||||
return &DNSProvider{client: client, config: config}, nil
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge.
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
zone, err := d.getHostedZone(domain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("sakuracloud: %v", err)
|
||||
}
|
||||
|
||||
name := d.extractRecordName(fqdn, zone.Name)
|
||||
|
||||
zone.AddRecord(zone.CreateNewRecord(name, "TXT", value, d.config.TTL))
|
||||
_, err = d.client.GetDNSAPI().Update(zone.ID, zone)
|
||||
if err != nil {
|
||||
return fmt.Errorf("sakuracloud: API call failed: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters.
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
zone, err := d.getHostedZone(domain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("sakuracloud: %v", err)
|
||||
}
|
||||
|
||||
records := d.findTxtRecords(fqdn, zone)
|
||||
|
||||
for _, record := range records {
|
||||
var updRecords []sacloud.DNSRecordSet
|
||||
for _, r := range zone.Settings.DNS.ResourceRecordSets {
|
||||
if !(r.Name == record.Name && r.Type == record.Type && r.RData == record.RData) {
|
||||
updRecords = append(updRecords, r)
|
||||
}
|
||||
}
|
||||
zone.Settings.DNS.ResourceRecordSets = updRecords
|
||||
}
|
||||
|
||||
_, err = d.client.GetDNSAPI().Update(zone.ID, zone)
|
||||
if err != nil {
|
||||
return fmt.Errorf("sakuracloud: API call failed: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Timeout returns the timeout and interval to use when checking for DNS propagation.
|
||||
// Adjusting here to cope with spikes in propagation times.
|
||||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
func (d *DNSProvider) getHostedZone(domain string) (*sacloud.DNS, error) {
|
||||
authZone, err := dns01.FindZoneByFqdn(dns01.ToFqdn(domain))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
zoneName := dns01.UnFqdn(authZone)
|
||||
|
||||
res, err := d.client.GetDNSAPI().WithNameLike(zoneName).Find()
|
||||
if err != nil {
|
||||
if notFound, ok := err.(api.Error); ok && notFound.ResponseCode() == http.StatusNotFound {
|
||||
return nil, fmt.Errorf("zone %s not found on SakuraCloud DNS: %v", zoneName, err)
|
||||
}
|
||||
return nil, fmt.Errorf("API call failed: %v", err)
|
||||
}
|
||||
|
||||
for _, zone := range res.CommonServiceDNSItems {
|
||||
if zone.Name == zoneName {
|
||||
return &zone, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("zone %s not found", zoneName)
|
||||
}
|
||||
|
||||
func (d *DNSProvider) findTxtRecords(fqdn string, zone *sacloud.DNS) []sacloud.DNSRecordSet {
|
||||
recordName := d.extractRecordName(fqdn, zone.Name)
|
||||
|
||||
var res []sacloud.DNSRecordSet
|
||||
for _, record := range zone.Settings.DNS.ResourceRecordSets {
|
||||
if record.Name == recordName && record.Type == "TXT" {
|
||||
res = append(res, record)
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (d *DNSProvider) extractRecordName(fqdn, domain string) string {
|
||||
name := dns01.UnFqdn(fqdn)
|
||||
if idx := strings.Index(name, "."+domain); idx != -1 {
|
||||
return name[:idx]
|
||||
}
|
||||
return name
|
||||
}
|
221
vendor/github.com/go-acme/lego/providers/dns/selectel/internal/client.go
generated
vendored
Normal file
221
vendor/github.com/go-acme/lego/providers/dns/selectel/internal/client.go
generated
vendored
Normal file
|
@ -0,0 +1,221 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Domain represents domain name.
|
||||
type Domain struct {
|
||||
ID int `json:"id,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
}
|
||||
|
||||
// Record represents DNS record.
|
||||
type Record struct {
|
||||
ID int `json:"id,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Type string `json:"type,omitempty"` // Record type (SOA, NS, A/AAAA, CNAME, SRV, MX, TXT, SPF)
|
||||
TTL int `json:"ttl,omitempty"`
|
||||
Email string `json:"email,omitempty"` // Email of domain's admin (only for SOA records)
|
||||
Content string `json:"content,omitempty"` // Record content (not for SRV)
|
||||
}
|
||||
|
||||
// APIError API error message
|
||||
type APIError struct {
|
||||
Description string `json:"error"`
|
||||
Code int `json:"code"`
|
||||
Field string `json:"field"`
|
||||
}
|
||||
|
||||
func (a *APIError) Error() string {
|
||||
return fmt.Sprintf("API error: %d - %s - %s", a.Code, a.Description, a.Field)
|
||||
}
|
||||
|
||||
// ClientOpts represents options to init client.
|
||||
type ClientOpts struct {
|
||||
BaseURL string
|
||||
Token string
|
||||
UserAgent string
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// Client represents DNS client.
|
||||
type Client struct {
|
||||
baseURL string
|
||||
token string
|
||||
userAgent string
|
||||
httpClient *http.Client
|
||||
}
|
||||
|
||||
// NewClient returns a client instance.
|
||||
func NewClient(opts ClientOpts) *Client {
|
||||
if opts.HTTPClient == nil {
|
||||
opts.HTTPClient = &http.Client{}
|
||||
}
|
||||
|
||||
return &Client{
|
||||
token: opts.Token,
|
||||
baseURL: opts.BaseURL,
|
||||
httpClient: opts.HTTPClient,
|
||||
userAgent: opts.UserAgent,
|
||||
}
|
||||
}
|
||||
|
||||
// GetDomainByName gets Domain object by its name. If `domainName` level > 2 and there is
|
||||
// no such domain on the account - it'll recursively search for the first
|
||||
// which is exists in Selectel Domain API.
|
||||
func (c *Client) GetDomainByName(domainName string) (*Domain, error) {
|
||||
uri := fmt.Sprintf("/%s", domainName)
|
||||
req, err := c.newRequest(http.MethodGet, uri, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
domain := &Domain{}
|
||||
resp, err := c.do(req, domain)
|
||||
if err != nil {
|
||||
switch {
|
||||
case resp.StatusCode == http.StatusNotFound && strings.Count(domainName, ".") > 1:
|
||||
// Look up for the next sub domain
|
||||
subIndex := strings.Index(domainName, ".")
|
||||
return c.GetDomainByName(domainName[subIndex+1:])
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return domain, nil
|
||||
}
|
||||
|
||||
// AddRecord adds Record for given domain.
|
||||
func (c *Client) AddRecord(domainID int, body Record) (*Record, error) {
|
||||
uri := fmt.Sprintf("/%d/records/", domainID)
|
||||
req, err := c.newRequest(http.MethodPost, uri, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
record := &Record{}
|
||||
_, err = c.do(req, record)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return record, nil
|
||||
}
|
||||
|
||||
// ListRecords returns list records for specific domain.
|
||||
func (c *Client) ListRecords(domainID int) ([]*Record, error) {
|
||||
uri := fmt.Sprintf("/%d/records/", domainID)
|
||||
req, err := c.newRequest(http.MethodGet, uri, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var records []*Record
|
||||
_, err = c.do(req, &records)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return records, nil
|
||||
}
|
||||
|
||||
// DeleteRecord deletes specific record.
|
||||
func (c *Client) DeleteRecord(domainID, recordID int) error {
|
||||
uri := fmt.Sprintf("/%d/records/%d", domainID, recordID)
|
||||
req, err := c.newRequest(http.MethodDelete, uri, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = c.do(req, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Client) newRequest(method, uri string, body interface{}) (*http.Request, error) {
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
if body != nil {
|
||||
err := json.NewEncoder(buf).Encode(body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to encode request body with error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, c.baseURL+uri, buf)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create new http request with error: %v", err)
|
||||
}
|
||||
|
||||
req.Header.Add("X-Token", c.token)
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
req.Header.Add("Accept", "application/json")
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func (c *Client) do(req *http.Request, to interface{}) (*http.Response, error) {
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("request failed with error: %v", err)
|
||||
}
|
||||
|
||||
err = checkResponse(resp)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
if to != nil {
|
||||
if err = unmarshalBody(resp, to); err != nil {
|
||||
return resp, err
|
||||
}
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func checkResponse(resp *http.Response) error {
|
||||
if resp.StatusCode >= http.StatusBadRequest &&
|
||||
resp.StatusCode <= http.StatusNetworkAuthenticationRequired {
|
||||
|
||||
if resp.Body == nil {
|
||||
return fmt.Errorf("request failed with status code %d and empty body", resp.StatusCode)
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
apiError := APIError{}
|
||||
err = json.Unmarshal(body, &apiError)
|
||||
if err != nil {
|
||||
return fmt.Errorf("request failed with status code %d, response body: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
return fmt.Errorf("request failed with status code %d: %v", resp.StatusCode, apiError)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func unmarshalBody(resp *http.Response, to interface{}) error {
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
err = json.Unmarshal(body, to)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unmarshaling error: %v: %s", err, string(body))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
154
vendor/github.com/go-acme/lego/providers/dns/selectel/selectel.go
generated
vendored
Normal file
154
vendor/github.com/go-acme/lego/providers/dns/selectel/selectel.go
generated
vendored
Normal file
|
@ -0,0 +1,154 @@
|
|||
// Package selectel implements a DNS provider for solving the DNS-01 challenge using Selectel Domains API.
|
||||
// Selectel Domain API reference: https://kb.selectel.com/23136054.html
|
||||
// Token: https://my.selectel.ru/profile/apikeys
|
||||
package selectel
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
"github.com/go-acme/lego/providers/dns/selectel/internal"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultBaseURL = "https://api.selectel.ru/domains/v1"
|
||||
minTTL = 60
|
||||
)
|
||||
|
||||
const (
|
||||
envNamespace = "SELECTEL_"
|
||||
baseURLEnvVar = envNamespace + "BASE_URL"
|
||||
apiTokenEnvVar = envNamespace + "API_TOKEN"
|
||||
ttlEnvVar = envNamespace + "TTL"
|
||||
propagationTimeoutEnvVar = envNamespace + "PROPAGATION_TIMEOUT"
|
||||
pollingIntervalEnvVar = envNamespace + "POLLING_INTERVAL"
|
||||
httpTimeoutEnvVar = envNamespace + "HTTP_TIMEOUT"
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider.
|
||||
type Config struct {
|
||||
BaseURL string
|
||||
Token string
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider.
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
BaseURL: env.GetOrDefaultString(baseURLEnvVar, defaultBaseURL),
|
||||
TTL: env.GetOrDefaultInt(ttlEnvVar, minTTL),
|
||||
PropagationTimeout: env.GetOrDefaultSecond(propagationTimeoutEnvVar, 120*time.Second),
|
||||
PollingInterval: env.GetOrDefaultSecond(pollingIntervalEnvVar, 2*time.Second),
|
||||
HTTPClient: &http.Client{
|
||||
Timeout: env.GetOrDefaultSecond(httpTimeoutEnvVar, 30*time.Second),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface.
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
client *internal.Client
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for Selectel Domains API.
|
||||
// API token must be passed in the environment variable SELECTEL_API_TOKEN.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get(apiTokenEnvVar)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("selectel: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.Token = values[apiTokenEnvVar]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for selectel.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("selectel: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if config.Token == "" {
|
||||
return nil, errors.New("selectel: credentials missing")
|
||||
}
|
||||
|
||||
if config.TTL < minTTL {
|
||||
return nil, fmt.Errorf("selectel: invalid TTL, TTL (%d) must be greater than %d", config.TTL, minTTL)
|
||||
}
|
||||
|
||||
client := internal.NewClient(internal.ClientOpts{
|
||||
BaseURL: config.BaseURL,
|
||||
Token: config.Token,
|
||||
HTTPClient: config.HTTPClient,
|
||||
})
|
||||
|
||||
return &DNSProvider{config: config, client: client}, nil
|
||||
}
|
||||
|
||||
// Timeout returns the Timeout and interval to use when checking for DNS propagation.
|
||||
// Adjusting here to cope with spikes in propagation times.
|
||||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfill DNS-01 challenge.
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
domainObj, err := d.client.GetDomainByName(domain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("selectel: %v", err)
|
||||
}
|
||||
|
||||
txtRecord := internal.Record{
|
||||
Type: "TXT",
|
||||
TTL: d.config.TTL,
|
||||
Name: fqdn,
|
||||
Content: value,
|
||||
}
|
||||
_, err = d.client.AddRecord(domainObj.ID, txtRecord)
|
||||
if err != nil {
|
||||
return fmt.Errorf("selectel: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes a TXT record used for DNS-01 challenge.
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
recordName := dns01.UnFqdn(fqdn)
|
||||
|
||||
domainObj, err := d.client.GetDomainByName(domain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("selectel: %v", err)
|
||||
}
|
||||
|
||||
records, err := d.client.ListRecords(domainObj.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("selectel: %v", err)
|
||||
}
|
||||
|
||||
// Delete records with specific FQDN
|
||||
var lastErr error
|
||||
for _, record := range records {
|
||||
if record.Name == recordName {
|
||||
err = d.client.DeleteRecord(domainObj.ID, record.ID)
|
||||
if err != nil {
|
||||
lastErr = fmt.Errorf("selectel: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return lastErr
|
||||
}
|
217
vendor/github.com/go-acme/lego/providers/dns/stackpath/client.go
generated
vendored
Normal file
217
vendor/github.com/go-acme/lego/providers/dns/stackpath/client.go
generated
vendored
Normal file
|
@ -0,0 +1,217 @@
|
|||
package stackpath
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"path"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"golang.org/x/net/publicsuffix"
|
||||
)
|
||||
|
||||
// Zones is the response struct from the Stackpath api GetZones
|
||||
type Zones struct {
|
||||
Zones []Zone `json:"zones"`
|
||||
}
|
||||
|
||||
// Zone a DNS zone representation
|
||||
type Zone struct {
|
||||
ID string
|
||||
Domain string
|
||||
}
|
||||
|
||||
// Records is the response struct from the Stackpath api GetZoneRecords
|
||||
type Records struct {
|
||||
Records []Record `json:"records"`
|
||||
}
|
||||
|
||||
// Record a DNS record representation
|
||||
type Record struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
TTL int `json:"ttl"`
|
||||
Data string `json:"data"`
|
||||
}
|
||||
|
||||
// ErrorResponse the API error response representation
|
||||
type ErrorResponse struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"error"`
|
||||
}
|
||||
|
||||
func (e *ErrorResponse) Error() string {
|
||||
return fmt.Sprintf("%d %s", e.Code, e.Message)
|
||||
}
|
||||
|
||||
// https://developer.stackpath.com/en/api/dns/#operation/GetZones
|
||||
func (d *DNSProvider) getZones(domain string) (*Zone, error) {
|
||||
domain = dns01.UnFqdn(domain)
|
||||
tld, err := publicsuffix.EffectiveTLDPlusOne(domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := d.newRequest(http.MethodGet, "/zones", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
query := req.URL.Query()
|
||||
query.Add("page_request.filter", fmt.Sprintf("domain='%s'", tld))
|
||||
req.URL.RawQuery = query.Encode()
|
||||
|
||||
var zones Zones
|
||||
err = d.do(req, &zones)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(zones.Zones) == 0 {
|
||||
return nil, fmt.Errorf("did not find zone with domain %s", domain)
|
||||
}
|
||||
|
||||
return &zones.Zones[0], nil
|
||||
}
|
||||
|
||||
// https://developer.stackpath.com/en/api/dns/#operation/GetZoneRecords
|
||||
func (d *DNSProvider) getZoneRecords(name string, zone *Zone) ([]Record, error) {
|
||||
u := fmt.Sprintf("/zones/%s/records", zone.ID)
|
||||
req, err := d.newRequest(http.MethodGet, u, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
query := req.URL.Query()
|
||||
query.Add("page_request.filter", fmt.Sprintf("name='%s' and type='TXT'", name))
|
||||
req.URL.RawQuery = query.Encode()
|
||||
|
||||
var records Records
|
||||
err = d.do(req, &records)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(records.Records) == 0 {
|
||||
return nil, fmt.Errorf("did not find record with name %s", name)
|
||||
}
|
||||
|
||||
return records.Records, nil
|
||||
}
|
||||
|
||||
// https://developer.stackpath.com/en/api/dns/#operation/CreateZoneRecord
|
||||
func (d *DNSProvider) createZoneRecord(zone *Zone, record Record) error {
|
||||
u := fmt.Sprintf("/zones/%s/records", zone.ID)
|
||||
req, err := d.newRequest(http.MethodPost, u, record)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return d.do(req, nil)
|
||||
}
|
||||
|
||||
// https://developer.stackpath.com/en/api/dns/#operation/DeleteZoneRecord
|
||||
func (d *DNSProvider) deleteZoneRecord(zone *Zone, record Record) error {
|
||||
u := fmt.Sprintf("/zones/%s/records/%s", zone.ID, record.ID)
|
||||
req, err := d.newRequest(http.MethodDelete, u, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return d.do(req, nil)
|
||||
}
|
||||
|
||||
func (d *DNSProvider) newRequest(method, urlStr string, body interface{}) (*http.Request, error) {
|
||||
u, err := d.BaseURL.Parse(path.Join(d.config.StackID, urlStr))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if body == nil {
|
||||
var req *http.Request
|
||||
req, err = http.NewRequest(method, u.String(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
reqBody, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, u.String(), bytes.NewBuffer(reqBody))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) do(req *http.Request, v interface{}) error {
|
||||
resp, err := d.client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = checkResponse(resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
raw, err := readBody(resp)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read body: %v", err)
|
||||
}
|
||||
|
||||
err = json.Unmarshal(raw, v)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unmarshaling error: %v: %s", err, string(raw))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkResponse(resp *http.Response) error {
|
||||
if resp.StatusCode > 299 {
|
||||
data, err := readBody(resp)
|
||||
if err != nil {
|
||||
return &ErrorResponse{Code: resp.StatusCode, Message: err.Error()}
|
||||
}
|
||||
|
||||
errResp := &ErrorResponse{}
|
||||
err = json.Unmarshal(data, errResp)
|
||||
if err != nil {
|
||||
return &ErrorResponse{Code: resp.StatusCode, Message: fmt.Sprintf("unmarshaling error: %v: %s", err, string(data))}
|
||||
}
|
||||
return errResp
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func readBody(resp *http.Response) ([]byte, error) {
|
||||
if resp.Body == nil {
|
||||
return nil, fmt.Errorf("response body is nil")
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
rawBody, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return rawBody, nil
|
||||
}
|
150
vendor/github.com/go-acme/lego/providers/dns/stackpath/stackpath.go
generated
vendored
Normal file
150
vendor/github.com/go-acme/lego/providers/dns/stackpath/stackpath.go
generated
vendored
Normal file
|
@ -0,0 +1,150 @@
|
|||
// Package stackpath implements a DNS provider for solving the DNS-01 challenge using Stackpath DNS.
|
||||
// https://developer.stackpath.com/en/api/dns/
|
||||
package stackpath
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/log"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
"golang.org/x/oauth2/clientcredentials"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultBaseURL = "https://gateway.stackpath.com/dns/v1/stacks/"
|
||||
defaultAuthURL = "https://gateway.stackpath.com/identity/v1/oauth2/token"
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
ClientID string
|
||||
ClientSecret string
|
||||
StackID string
|
||||
TTL int
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt("STACKPATH_TTL", 120),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("STACKPATH_PROPAGATION_TIMEOUT", dns01.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("STACKPATH_POLLING_INTERVAL", dns01.DefaultPollingInterval),
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface.
|
||||
type DNSProvider struct {
|
||||
BaseURL *url.URL
|
||||
client *http.Client
|
||||
config *Config
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for Stackpath.
|
||||
// Credentials must be passed in the environment variables:
|
||||
// STACKPATH_CLIENT_ID, STACKPATH_CLIENT_SECRET, and STACKPATH_STACK_ID.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("STACKPATH_CLIENT_ID", "STACKPATH_CLIENT_SECRET", "STACKPATH_STACK_ID")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("stackpath: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.ClientID = values["STACKPATH_CLIENT_ID"]
|
||||
config.ClientSecret = values["STACKPATH_CLIENT_SECRET"]
|
||||
config.StackID = values["STACKPATH_STACK_ID"]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for Stackpath.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("stackpath: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if len(config.ClientID) == 0 || len(config.ClientSecret) == 0 {
|
||||
return nil, errors.New("stackpath: credentials missing")
|
||||
}
|
||||
|
||||
if len(config.StackID) == 0 {
|
||||
return nil, errors.New("stackpath: stack id missing")
|
||||
}
|
||||
|
||||
baseURL, _ := url.Parse(defaultBaseURL)
|
||||
|
||||
return &DNSProvider{
|
||||
BaseURL: baseURL,
|
||||
client: getOathClient(config),
|
||||
config: config,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getOathClient(config *Config) *http.Client {
|
||||
oathConfig := &clientcredentials.Config{
|
||||
TokenURL: defaultAuthURL,
|
||||
ClientID: config.ClientID,
|
||||
ClientSecret: config.ClientSecret,
|
||||
}
|
||||
|
||||
return oathConfig.Client(context.Background())
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
zone, err := d.getZones(domain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("stackpath: %v", err)
|
||||
}
|
||||
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
parts := strings.Split(fqdn, ".")
|
||||
|
||||
record := Record{
|
||||
Name: parts[0],
|
||||
Type: "TXT",
|
||||
TTL: d.config.TTL,
|
||||
Data: value,
|
||||
}
|
||||
|
||||
return d.createZoneRecord(zone, record)
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
zone, err := d.getZones(domain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("stackpath: %v", err)
|
||||
}
|
||||
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
parts := strings.Split(fqdn, ".")
|
||||
|
||||
records, err := d.getZoneRecords(parts[0], zone)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, record := range records {
|
||||
err = d.deleteZoneRecord(zone, record)
|
||||
if err != nil {
|
||||
log.Printf("stackpath: failed to delete TXT record: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Timeout returns the timeout and interval to use when checking for DNS propagation.
|
||||
// Adjusting here to cope with spikes in propagation times.
|
||||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
160
vendor/github.com/go-acme/lego/providers/dns/transip/transip.go
generated
vendored
Normal file
160
vendor/github.com/go-acme/lego/providers/dns/transip/transip.go
generated
vendored
Normal file
|
@ -0,0 +1,160 @@
|
|||
// Package transip implements a DNS provider for solving the DNS-01 challenge using TransIP.
|
||||
package transip
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
"github.com/transip/gotransip"
|
||||
transipdomain "github.com/transip/gotransip/domain"
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
AccountName string
|
||||
PrivateKeyPath string
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int64
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: int64(env.GetOrDefaultInt("TRANSIP_TTL", 10)),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("TRANSIP_PROPAGATION_TIMEOUT", 10*time.Minute),
|
||||
PollingInterval: env.GetOrDefaultSecond("TRANSIP_POLLING_INTERVAL", 10*time.Second),
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider describes a provider for TransIP
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
client gotransip.Client
|
||||
dnsEntriesMu sync.Mutex
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for TransIP.
|
||||
// Credentials must be passed in the environment variables:
|
||||
// TRANSIP_ACCOUNTNAME, TRANSIP_PRIVATEKEYPATH.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("TRANSIP_ACCOUNT_NAME", "TRANSIP_PRIVATE_KEY_PATH")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("transip: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.AccountName = values["TRANSIP_ACCOUNT_NAME"]
|
||||
config.PrivateKeyPath = values["TRANSIP_PRIVATE_KEY_PATH"]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for TransIP.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("transip: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
client, err := gotransip.NewSOAPClient(gotransip.ClientConfig{
|
||||
AccountName: config.AccountName,
|
||||
PrivateKeyPath: config.PrivateKeyPath,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("transip: %v", err)
|
||||
}
|
||||
|
||||
return &DNSProvider{client: client, config: config}, nil
|
||||
}
|
||||
|
||||
// Timeout returns the timeout and interval to use when checking for DNS propagation.
|
||||
// Adjusting here to cope with spikes in propagation times.
|
||||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
authZone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
domainName := dns01.UnFqdn(authZone)
|
||||
|
||||
// get the subDomain
|
||||
subDomain := strings.TrimSuffix(dns01.UnFqdn(fqdn), "."+domainName)
|
||||
|
||||
// use mutex to prevent race condition from GetInfo until SetDNSEntries
|
||||
d.dnsEntriesMu.Lock()
|
||||
defer d.dnsEntriesMu.Unlock()
|
||||
|
||||
// get all DNS entries
|
||||
info, err := transipdomain.GetInfo(d.client, domainName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("transip: error for %s in Present: %v", domain, err)
|
||||
}
|
||||
|
||||
// include the new DNS entry
|
||||
dnsEntries := append(info.DNSEntries, transipdomain.DNSEntry{
|
||||
Name: subDomain,
|
||||
TTL: d.config.TTL,
|
||||
Type: transipdomain.DNSEntryTypeTXT,
|
||||
Content: value,
|
||||
})
|
||||
|
||||
// set the updated DNS entries
|
||||
err = transipdomain.SetDNSEntries(d.client, domainName, dnsEntries)
|
||||
if err != nil {
|
||||
return fmt.Errorf("transip: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
authZone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
domainName := dns01.UnFqdn(authZone)
|
||||
|
||||
// get the subDomain
|
||||
subDomain := strings.TrimSuffix(dns01.UnFqdn(fqdn), "."+domainName)
|
||||
|
||||
// use mutex to prevent race condition from GetInfo until SetDNSEntries
|
||||
d.dnsEntriesMu.Lock()
|
||||
defer d.dnsEntriesMu.Unlock()
|
||||
|
||||
// get all DNS entries
|
||||
info, err := transipdomain.GetInfo(d.client, domainName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("transip: error for %s in CleanUp: %v", fqdn, err)
|
||||
}
|
||||
|
||||
// loop through the existing entries and remove the specific record
|
||||
updatedEntries := info.DNSEntries[:0]
|
||||
for _, e := range info.DNSEntries {
|
||||
if e.Name != subDomain {
|
||||
updatedEntries = append(updatedEntries, e)
|
||||
}
|
||||
}
|
||||
|
||||
// set the updated DNS entries
|
||||
err = transipdomain.SetDNSEntries(d.client, domainName, updatedEntries)
|
||||
if err != nil {
|
||||
return fmt.Errorf("transip: couldn't get Record ID in CleanUp: %sv", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
113
vendor/github.com/go-acme/lego/providers/dns/vegadns/vegadns.go
generated
vendored
Normal file
113
vendor/github.com/go-acme/lego/providers/dns/vegadns/vegadns.go
generated
vendored
Normal file
|
@ -0,0 +1,113 @@
|
|||
// Package vegadns implements a DNS provider for solving the DNS-01 challenge using VegaDNS.
|
||||
package vegadns
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
vegaClient "github.com/OpenDNS/vegadns2client"
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
BaseURL string
|
||||
APIKey string
|
||||
APISecret string
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt("VEGADNS_TTL", 10),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("VEGADNS_PROPAGATION_TIMEOUT", 12*time.Minute),
|
||||
PollingInterval: env.GetOrDefaultSecond("VEGADNS_POLLING_INTERVAL", 1*time.Minute),
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider describes a provider for VegaDNS
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
client vegaClient.VegaDNSClient
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for VegaDNS.
|
||||
// Credentials must be passed in the environment variables:
|
||||
// VEGADNS_URL, SECRET_VEGADNS_KEY, SECRET_VEGADNS_SECRET.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("VEGADNS_URL")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("vegadns: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.BaseURL = values["VEGADNS_URL"]
|
||||
config.APIKey = env.GetOrFile("SECRET_VEGADNS_KEY")
|
||||
config.APISecret = env.GetOrFile("SECRET_VEGADNS_SECRET")
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for VegaDNS.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("vegadns: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
vega := vegaClient.NewVegaDNSClient(config.BaseURL)
|
||||
vega.APIKey = config.APIKey
|
||||
vega.APISecret = config.APISecret
|
||||
|
||||
return &DNSProvider{client: vega, config: config}, nil
|
||||
}
|
||||
|
||||
// Timeout returns the timeout and interval to use when checking for DNS propagation.
|
||||
// Adjusting here to cope with spikes in propagation times.
|
||||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
_, domainID, err := d.client.GetAuthZone(fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("vegadns: can't find Authoritative Zone for %s in Present: %v", fqdn, err)
|
||||
}
|
||||
|
||||
err = d.client.CreateTXT(domainID, fqdn, value, d.config.TTL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("vegadns: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
_, domainID, err := d.client.GetAuthZone(fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("vegadns: can't find Authoritative Zone for %s in CleanUp: %v", fqdn, err)
|
||||
}
|
||||
|
||||
txt := strings.TrimSuffix(fqdn, ".")
|
||||
|
||||
recordID, err := d.client.GetRecordID(domainID, txt, "TXT")
|
||||
if err != nil {
|
||||
return fmt.Errorf("vegadns: couldn't get Record ID in CleanUp: %s", err)
|
||||
}
|
||||
|
||||
err = d.client.DeleteRecord(recordID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("vegadns: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
221
vendor/github.com/go-acme/lego/providers/dns/vscale/internal/client.go
generated
vendored
Normal file
221
vendor/github.com/go-acme/lego/providers/dns/vscale/internal/client.go
generated
vendored
Normal file
|
@ -0,0 +1,221 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Domain represents domain name.
|
||||
type Domain struct {
|
||||
ID int `json:"id,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
}
|
||||
|
||||
// Record represents DNS record.
|
||||
type Record struct {
|
||||
ID int `json:"id,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Type string `json:"type,omitempty"` // Record type (SOA, NS, A/AAAA, CNAME, SRV, MX, TXT, SPF)
|
||||
TTL int `json:"ttl,omitempty"`
|
||||
Email string `json:"email,omitempty"` // Email of domain's admin (only for SOA records)
|
||||
Content string `json:"content,omitempty"` // Record content (not for SRV)
|
||||
}
|
||||
|
||||
// APIError API error message
|
||||
type APIError struct {
|
||||
Description string `json:"error"`
|
||||
Code int `json:"code"`
|
||||
Field string `json:"field"`
|
||||
}
|
||||
|
||||
func (a *APIError) Error() string {
|
||||
return fmt.Sprintf("API error: %d - %s - %s", a.Code, a.Description, a.Field)
|
||||
}
|
||||
|
||||
// ClientOpts represents options to init client.
|
||||
type ClientOpts struct {
|
||||
BaseURL string
|
||||
Token string
|
||||
UserAgent string
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// Client represents DNS client.
|
||||
type Client struct {
|
||||
baseURL string
|
||||
token string
|
||||
userAgent string
|
||||
httpClient *http.Client
|
||||
}
|
||||
|
||||
// NewClient returns a client instance.
|
||||
func NewClient(opts ClientOpts) *Client {
|
||||
if opts.HTTPClient == nil {
|
||||
opts.HTTPClient = &http.Client{}
|
||||
}
|
||||
|
||||
return &Client{
|
||||
token: opts.Token,
|
||||
baseURL: opts.BaseURL,
|
||||
httpClient: opts.HTTPClient,
|
||||
userAgent: opts.UserAgent,
|
||||
}
|
||||
}
|
||||
|
||||
// GetDomainByName gets Domain object by its name. If `domainName` level > 2 and there is
|
||||
// no such domain on the account - it'll recursively search for the first
|
||||
// which is exists in Vscale Domains API.
|
||||
func (c *Client) GetDomainByName(domainName string) (*Domain, error) {
|
||||
uri := fmt.Sprintf("/%s", domainName)
|
||||
req, err := c.newRequest(http.MethodGet, uri, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
domain := &Domain{}
|
||||
resp, err := c.do(req, domain)
|
||||
if err != nil {
|
||||
switch {
|
||||
case resp.StatusCode == http.StatusNotFound && strings.Count(domainName, ".") > 1:
|
||||
// Look up for the next sub domain
|
||||
subIndex := strings.Index(domainName, ".")
|
||||
return c.GetDomainByName(domainName[subIndex+1:])
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return domain, nil
|
||||
}
|
||||
|
||||
// AddRecord adds Record for given domain.
|
||||
func (c *Client) AddRecord(domainID int, body Record) (*Record, error) {
|
||||
uri := fmt.Sprintf("/%d/records/", domainID)
|
||||
req, err := c.newRequest(http.MethodPost, uri, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
record := &Record{}
|
||||
_, err = c.do(req, record)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return record, nil
|
||||
}
|
||||
|
||||
// ListRecords returns list records for specific domain.
|
||||
func (c *Client) ListRecords(domainID int) ([]*Record, error) {
|
||||
uri := fmt.Sprintf("/%d/records/", domainID)
|
||||
req, err := c.newRequest(http.MethodGet, uri, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var records []*Record
|
||||
_, err = c.do(req, &records)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return records, nil
|
||||
}
|
||||
|
||||
// DeleteRecord deletes specific record.
|
||||
func (c *Client) DeleteRecord(domainID, recordID int) error {
|
||||
uri := fmt.Sprintf("/%d/records/%d", domainID, recordID)
|
||||
req, err := c.newRequest(http.MethodDelete, uri, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = c.do(req, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Client) newRequest(method, uri string, body interface{}) (*http.Request, error) {
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
if body != nil {
|
||||
err := json.NewEncoder(buf).Encode(body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to encode request body with error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, c.baseURL+uri, buf)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create new http request with error: %v", err)
|
||||
}
|
||||
|
||||
req.Header.Add("X-Token", c.token)
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
req.Header.Add("Accept", "application/json")
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func (c *Client) do(req *http.Request, to interface{}) (*http.Response, error) {
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("request failed with error: %v", err)
|
||||
}
|
||||
|
||||
err = checkResponse(resp)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
if to != nil {
|
||||
if err = unmarshalBody(resp, to); err != nil {
|
||||
return resp, err
|
||||
}
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func checkResponse(resp *http.Response) error {
|
||||
if resp.StatusCode >= http.StatusBadRequest &&
|
||||
resp.StatusCode <= http.StatusNetworkAuthenticationRequired {
|
||||
|
||||
if resp.Body == nil {
|
||||
return fmt.Errorf("request failed with status code %d and empty body", resp.StatusCode)
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
apiError := APIError{}
|
||||
err = json.Unmarshal(body, &apiError)
|
||||
if err != nil {
|
||||
return fmt.Errorf("request failed with status code %d, response body: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
return fmt.Errorf("request failed with status code %d: %v", resp.StatusCode, apiError)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func unmarshalBody(resp *http.Response, to interface{}) error {
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
err = json.Unmarshal(body, to)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unmarshaling error: %v: %s", err, string(body))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
155
vendor/github.com/go-acme/lego/providers/dns/vscale/vscale.go
generated
vendored
Normal file
155
vendor/github.com/go-acme/lego/providers/dns/vscale/vscale.go
generated
vendored
Normal file
|
@ -0,0 +1,155 @@
|
|||
// Package vscale implements a DNS provider for solving the DNS-01 challenge using Vscale Domains API.
|
||||
// Vscale Domain API reference: https://developers.vscale.io/documentation/api/v1/#api-Domains
|
||||
// Token: https://vscale.io/panel/settings/tokens/
|
||||
package vscale
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/providers/dns/vscale/internal"
|
||||
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultBaseURL = "https://api.vscale.io/v1/domains"
|
||||
minTTL = 60
|
||||
)
|
||||
|
||||
const (
|
||||
envNamespace = "VSCALE_"
|
||||
baseURLEnvVar = envNamespace + "BASE_URL"
|
||||
apiTokenEnvVar = envNamespace + "API_TOKEN"
|
||||
ttlEnvVar = envNamespace + "TTL"
|
||||
propagationTimeoutEnvVar = envNamespace + "PROPAGATION_TIMEOUT"
|
||||
pollingIntervalEnvVar = envNamespace + "POLLING_INTERVAL"
|
||||
httpTimeoutEnvVar = envNamespace + "HTTP_TIMEOUT"
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider.
|
||||
type Config struct {
|
||||
BaseURL string
|
||||
Token string
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider.
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
BaseURL: env.GetOrDefaultString(baseURLEnvVar, defaultBaseURL),
|
||||
TTL: env.GetOrDefaultInt(ttlEnvVar, minTTL),
|
||||
PropagationTimeout: env.GetOrDefaultSecond(propagationTimeoutEnvVar, 120*time.Second),
|
||||
PollingInterval: env.GetOrDefaultSecond(pollingIntervalEnvVar, 2*time.Second),
|
||||
HTTPClient: &http.Client{
|
||||
Timeout: env.GetOrDefaultSecond(httpTimeoutEnvVar, 30*time.Second),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface.
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
client *internal.Client
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for Vscale Domains API.
|
||||
// API token must be passed in the environment variable VSCALE_API_TOKEN.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get(apiTokenEnvVar)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("vscale: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.Token = values[apiTokenEnvVar]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for Vscale.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("vscale: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if config.Token == "" {
|
||||
return nil, errors.New("vscale: credentials missing")
|
||||
}
|
||||
|
||||
if config.TTL < minTTL {
|
||||
return nil, fmt.Errorf("vscale: invalid TTL, TTL (%d) must be greater than %d", config.TTL, minTTL)
|
||||
}
|
||||
|
||||
client := internal.NewClient(internal.ClientOpts{
|
||||
BaseURL: config.BaseURL,
|
||||
Token: config.Token,
|
||||
HTTPClient: config.HTTPClient,
|
||||
})
|
||||
|
||||
return &DNSProvider{config: config, client: client}, nil
|
||||
}
|
||||
|
||||
// Timeout returns the Timeout and interval to use when checking for DNS propagation.
|
||||
// Adjusting here to cope with spikes in propagation times.
|
||||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfill DNS-01 challenge.
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
domainObj, err := d.client.GetDomainByName(domain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("vscale: %v", err)
|
||||
}
|
||||
|
||||
txtRecord := internal.Record{
|
||||
Type: "TXT",
|
||||
TTL: d.config.TTL,
|
||||
Name: fqdn,
|
||||
Content: value,
|
||||
}
|
||||
_, err = d.client.AddRecord(domainObj.ID, txtRecord)
|
||||
if err != nil {
|
||||
return fmt.Errorf("vscale: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes a TXT record used for DNS-01 challenge.
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
recordName := dns01.UnFqdn(fqdn)
|
||||
|
||||
domainObj, err := d.client.GetDomainByName(domain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("vscale: %v", err)
|
||||
}
|
||||
|
||||
records, err := d.client.ListRecords(domainObj.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("vscale: %v", err)
|
||||
}
|
||||
|
||||
// Delete records with specific FQDN
|
||||
var lastErr error
|
||||
for _, record := range records {
|
||||
if record.Name == recordName {
|
||||
err = d.client.DeleteRecord(domainObj.ID, record.ID)
|
||||
if err != nil {
|
||||
lastErr = fmt.Errorf("vscale: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return lastErr
|
||||
}
|
179
vendor/github.com/go-acme/lego/providers/dns/vultr/vultr.go
generated
vendored
Normal file
179
vendor/github.com/go-acme/lego/providers/dns/vultr/vultr.go
generated
vendored
Normal file
|
@ -0,0 +1,179 @@
|
|||
// 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 (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
vultr "github.com/JamesClonk/vultr/lib"
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
APIKey string
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt("VULTR_TTL", dns01.DefaultTTL),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("VULTR_PROPAGATION_TIMEOUT", dns01.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("VULTR_POLLING_INTERVAL", dns01.DefaultPollingInterval),
|
||||
HTTPClient: &http.Client{
|
||||
Timeout: env.GetOrDefaultSecond("VULTR_HTTP_TIMEOUT", 0),
|
||||
// from Vultr Client
|
||||
Transport: &http.Transport{
|
||||
TLSNextProto: make(map[string]func(string, *tls.Conn) http.RoundTripper),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface.
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
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) {
|
||||
values, err := env.Get("VULTR_API_KEY")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("vultr: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.APIKey = values["VULTR_API_KEY"]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for Vultr.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("vultr: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if config.APIKey == "" {
|
||||
return nil, fmt.Errorf("vultr: credentials missing")
|
||||
}
|
||||
|
||||
options := &vultr.Options{
|
||||
HTTPClient: config.HTTPClient,
|
||||
}
|
||||
client := vultr.NewClient(config.APIKey, options)
|
||||
|
||||
return &DNSProvider{client: client, config: config}, nil
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfill the DNS-01 challenge.
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
zoneDomain, err := d.getHostedZone(domain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("vultr: %v", err)
|
||||
}
|
||||
|
||||
name := d.extractRecordName(fqdn, zoneDomain)
|
||||
|
||||
err = d.client.CreateDNSRecord(zoneDomain, name, "TXT", `"`+value+`"`, 0, d.config.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 (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
zoneDomain, records, err := d.findTxtRecords(domain, fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("vultr: %v", err)
|
||||
}
|
||||
|
||||
var allErr []string
|
||||
for _, rec := range records {
|
||||
err := d.client.DeleteDNSRecord(zoneDomain, rec.RecordID)
|
||||
if err != nil {
|
||||
allErr = append(allErr, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
if len(allErr) > 0 {
|
||||
return errors.New(strings.Join(allErr, ": "))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Timeout returns the timeout and interval to use when checking for DNS propagation.
|
||||
// Adjusting here to cope with spikes in propagation times.
|
||||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
func (d *DNSProvider) getHostedZone(domain string) (string, error) {
|
||||
domains, err := d.client.GetDNSDomains()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("API call failed: %v", err)
|
||||
}
|
||||
|
||||
var hostedDomain vultr.DNSDomain
|
||||
for _, dom := range domains {
|
||||
if strings.HasSuffix(domain, dom.Domain) {
|
||||
if len(dom.Domain) > len(hostedDomain.Domain) {
|
||||
hostedDomain = dom
|
||||
}
|
||||
}
|
||||
}
|
||||
if hostedDomain.Domain == "" {
|
||||
return "", fmt.Errorf("no matching Vultr domain found for domain %s", domain)
|
||||
}
|
||||
|
||||
return hostedDomain.Domain, nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) findTxtRecords(domain, fqdn string) (string, []vultr.DNSRecord, error) {
|
||||
zoneDomain, err := d.getHostedZone(domain)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
var records []vultr.DNSRecord
|
||||
result, err := d.client.GetDNSRecords(zoneDomain)
|
||||
if err != nil {
|
||||
return "", records, fmt.Errorf("API call has failed: %v", err)
|
||||
}
|
||||
|
||||
recordName := d.extractRecordName(fqdn, zoneDomain)
|
||||
for _, record := range result {
|
||||
if record.Type == "TXT" && record.Name == recordName {
|
||||
records = append(records, record)
|
||||
}
|
||||
}
|
||||
|
||||
return zoneDomain, records, nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) extractRecordName(fqdn, domain string) string {
|
||||
name := dns01.UnFqdn(fqdn)
|
||||
if idx := strings.Index(name, "."+domain); idx != -1 {
|
||||
return name[:idx]
|
||||
}
|
||||
return name
|
||||
}
|
132
vendor/github.com/go-acme/lego/providers/dns/zoneee/client.go
generated
vendored
Normal file
132
vendor/github.com/go-acme/lego/providers/dns/zoneee/client.go
generated
vendored
Normal file
|
@ -0,0 +1,132 @@
|
|||
package zoneee
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"path"
|
||||
)
|
||||
|
||||
const defaultEndpoint = "https://api.zone.eu/v2/dns/"
|
||||
|
||||
type txtRecord struct {
|
||||
// Identifier (identificator)
|
||||
ID string `json:"id,omitempty"`
|
||||
// Hostname
|
||||
Name string `json:"name"`
|
||||
// TXT content value
|
||||
Destination string `json:"destination"`
|
||||
// Can this record be deleted
|
||||
Delete bool `json:"delete,omitempty"`
|
||||
// Can this record be modified
|
||||
Modify bool `json:"modify,omitempty"`
|
||||
// API url to get this entity
|
||||
ResourceURL string `json:"resource_url,omitempty"`
|
||||
}
|
||||
|
||||
func (d *DNSProvider) addTxtRecord(domain string, record txtRecord) ([]txtRecord, error) {
|
||||
reqBody := &bytes.Buffer{}
|
||||
if err := json.NewEncoder(reqBody).Encode(record); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := d.makeRequest(http.MethodPost, path.Join(domain, "txt"), reqBody)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var resp []txtRecord
|
||||
if err := d.sendRequest(req, &resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) getTxtRecords(domain string) ([]txtRecord, error) {
|
||||
req, err := d.makeRequest(http.MethodGet, path.Join(domain, "txt"), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var resp []txtRecord
|
||||
if err := d.sendRequest(req, &resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) removeTxtRecord(domain, id string) error {
|
||||
req, err := d.makeRequest(http.MethodDelete, path.Join(domain, "txt", id), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return d.sendRequest(req, nil)
|
||||
}
|
||||
|
||||
func (d *DNSProvider) makeRequest(method, resource string, body io.Reader) (*http.Request, error) {
|
||||
uri, err := d.config.Endpoint.Parse(resource)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, uri.String(), body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.SetBasicAuth(d.config.Username, d.config.APIKey)
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) sendRequest(req *http.Request, result interface{}) error {
|
||||
resp, err := d.config.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = checkResponse(resp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
if result == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
raw, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(raw, result)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unmarshaling %T error [status code=%d]: %v: %s", result, resp.StatusCode, err, string(raw))
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func checkResponse(resp *http.Response) error {
|
||||
if resp.StatusCode < http.StatusBadRequest {
|
||||
return nil
|
||||
}
|
||||
|
||||
if resp.Body == nil {
|
||||
return fmt.Errorf("response body is nil, status code=%d", resp.StatusCode)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
raw, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to read body: status code=%d, error=%v", resp.StatusCode, err)
|
||||
}
|
||||
|
||||
return fmt.Errorf("status code=%d: %s", resp.StatusCode, string(raw))
|
||||
}
|
134
vendor/github.com/go-acme/lego/providers/dns/zoneee/zoneee.go
generated
vendored
Normal file
134
vendor/github.com/go-acme/lego/providers/dns/zoneee/zoneee.go
generated
vendored
Normal file
|
@ -0,0 +1,134 @@
|
|||
// Package zoneee implements a DNS provider for solving the DNS-01 challenge through zone.ee.
|
||||
package zoneee
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
Endpoint *url.URL
|
||||
Username string
|
||||
APIKey string
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
endpoint, _ := url.Parse(defaultEndpoint)
|
||||
|
||||
return &Config{
|
||||
Endpoint: endpoint,
|
||||
// zone.ee can take up to 5min to propagate according to the support
|
||||
PropagationTimeout: env.GetOrDefaultSecond("ZONEEE_PROPAGATION_TIMEOUT", 5*time.Minute),
|
||||
PollingInterval: env.GetOrDefaultSecond("ZONEEE_POLLING_INTERVAL", 5*time.Second),
|
||||
HTTPClient: &http.Client{
|
||||
Timeout: env.GetOrDefaultSecond("ZONEEE_HTTP_TIMEOUT", 30*time.Second),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider describes a provider for acme-proxy
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("ZONEEE_API_USER", "ZONEEE_API_KEY")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("zoneee: %v", err)
|
||||
}
|
||||
|
||||
rawEndpoint := env.GetOrDefaultString("ZONEEE_ENDPOINT", defaultEndpoint)
|
||||
endpoint, err := url.Parse(rawEndpoint)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("zoneee: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.Username = values["ZONEEE_API_USER"]
|
||||
config.APIKey = values["ZONEEE_API_KEY"]
|
||||
config.Endpoint = endpoint
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider .
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("zoneee: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if config.Username == "" {
|
||||
return nil, fmt.Errorf("zoneee: credentials missing: username")
|
||||
}
|
||||
|
||||
if config.APIKey == "" {
|
||||
return nil, fmt.Errorf("zoneee: credentials missing: API key")
|
||||
}
|
||||
|
||||
if config.Endpoint == nil {
|
||||
return nil, errors.New("zoneee: the endpoint is missing")
|
||||
}
|
||||
|
||||
return &DNSProvider{config: config}, nil
|
||||
}
|
||||
|
||||
// Timeout returns the timeout and interval to use when checking for DNS propagation.
|
||||
// Adjusting here to cope with spikes in propagation times.
|
||||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
record := txtRecord{
|
||||
Name: fqdn[:len(fqdn)-1],
|
||||
Destination: value,
|
||||
}
|
||||
|
||||
_, err := d.addTxtRecord(domain, record)
|
||||
if err != nil {
|
||||
return fmt.Errorf("zoneee: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record previously created
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
_, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
records, err := d.getTxtRecords(domain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("zoneee: %v", err)
|
||||
}
|
||||
|
||||
var id string
|
||||
for _, record := range records {
|
||||
if record.Destination == value {
|
||||
id = record.ID
|
||||
}
|
||||
}
|
||||
|
||||
if id == "" {
|
||||
return fmt.Errorf("zoneee: txt record does not exist for %v", value)
|
||||
}
|
||||
|
||||
if err = d.removeTxtRecord(domain, id); err != nil {
|
||||
return fmt.Errorf("zoneee: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue