Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added multiple DNS provider support #331

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions acme/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,14 +104,18 @@ func (c *Client) SetChallengeProvider(challenge Challenge, p ChallengeProvider)
c.solvers[challenge] = &httpChallenge{jws: c.jws, validate: validate, provider: p}
case TLSSNI01:
c.solvers[challenge] = &tlsSNIChallenge{jws: c.jws, validate: validate, provider: p}
case DNS01:
c.solvers[challenge] = &dnsChallenge{jws: c.jws, validate: validate, provider: p}
default:
return fmt.Errorf("Unknown challenge %v", challenge)
}
return nil
}

// SetChallengeProvidersDNS specifies one or more provider p that can solve the DNS challenge type.
func (c *Client) SetChallengeProvidersDNS(challenge Challenge, p []ChallengeProvider) error {
c.solvers[challenge] = &dnsChallenge{jws: c.jws, validate: validate, providers: p}
return nil
}

// SetHTTPAddress specifies a custom interface:port to be used for HTTP based challenges.
// If this option is not used, the default port 80 and all interfaces will be used.
// To only specify a port and no interface use the ":port" notation.
Expand Down
49 changes: 35 additions & 14 deletions acme/dns_challenge.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"net"
"strings"
"time"

"github.com/miekg/dns"
"golang.org/x/net/publicsuffix"
)
Expand Down Expand Up @@ -69,13 +68,13 @@ func DNS01Record(domain, keyAuth string) (fqdn string, value string, ttl int) {
type dnsChallenge struct {
jws *jws
validate validateFunc
provider ChallengeProvider
providers []ChallengeProvider
}

func (s *dnsChallenge) Solve(chlng challenge, domain string) error {
logf("[INFO][%s] acme: Trying to solve DNS-01", domain)

if s.provider == nil {
if len(s.providers) == 0 {
return errors.New("No DNS Provider configured")
}

Expand All @@ -85,14 +84,22 @@ func (s *dnsChallenge) Solve(chlng challenge, domain string) error {
return err
}

err = s.provider.Present(domain, chlng.Token, keyAuth)
if err != nil {
return fmt.Errorf("Error presenting token: %s", err)
for _, v := range s.providers {
err = v.Present(domain, chlng.Token, keyAuth)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would try to Present() every domain on every provider. Shouldn't we have a way to tell which provider a certain domain uses?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I didn't thought about this use case. But normally you have one or more DNS provider per Domain. I designed the function that way. This means if you want to validate something.url.com, and you have two DNS providers, you can add both to set the right TXT record. With the old system, it was not possible to know which DNS provider will serve the answer and validation could be invalid. I'm not sure if we want to provide a mapping per domain and DNS provider. Actually I don't see such a use case.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ManuelGysin I see where you are coming from. But I also see the use case where people want to add domains to a SAN certificate which are hosted by different DNS providers.


if err != nil {
return fmt.Errorf("Error presenting token: %s", err)
}
}

defer func() {
err := s.provider.CleanUp(domain, chlng.Token, keyAuth)
if err != nil {
log.Printf("Error cleaning up %s: %v ", domain, err)

for _, v := range s.providers {
err := v.CleanUp(domain, chlng.Token, keyAuth)

if err != nil {
log.Printf("Error cleaning up %s: %v ", domain, err)
}
}
}()

Expand All @@ -101,16 +108,30 @@ func (s *dnsChallenge) Solve(chlng challenge, domain string) error {
logf("[INFO][%s] Checking DNS record propagation using %+v", domain, RecursiveNameservers)

var timeout, interval time.Duration
switch provider := s.provider.(type) {
case ChallengeProviderTimeout:
timeout, interval = provider.Timeout()
default:
timeout, interval = 60*time.Second, 2*time.Second

for _, v := range s.providers {
var tiou, inter time.Duration

switch provider := v.(type) {
case ChallengeProviderTimeout:
tiou, inter = provider.Timeout()
default:
tiou, inter = 60*time.Second, 2*time.Second
}

// Check if interval or timeout is greater, we take the highest number
if timeout < tiou {
timeout = tiou
}
if interval < inter {
interval = inter
}
}

err = WaitFor(timeout, interval, func() (bool, error) {
return PreCheckDNS(fqdn, value)
})

if err != nil {
return err
}
Expand Down
3 changes: 3 additions & 0 deletions cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,9 @@ Here is an example bash command using the CloudFlare DNS provider:
CLOUDFLARE_API_KEY=b9841238feb177a84330febba8a83208921177bffe733 \
lego --dns cloudflare --domains www.example.com --email me@bar.com run

Here is an example bash command using multiple DNS provider:
$ lego --dns gcloud,route53 --domains www.example.com --email me@bar.com run

`)

w := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', 0)
Expand Down
16 changes: 12 additions & 4 deletions cli_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,12 +114,20 @@ func setup(c *cli.Context) (*Configuration, *Account, *acme.Client) {
}

if c.GlobalIsSet("dns") {
provider, err := dns.NewDNSChallengeProviderByName(c.GlobalString("dns"))
if err != nil {
logger().Fatal(err)
// Get providers
var providers []acme.ChallengeProvider

for _, v := range strings.Split(c.GlobalString("dns"), ",") {
provider, err := dns.NewDNSChallengeProviderByName(v)

if err != nil {
logger().Fatal(err)
}

providers = append(providers, provider)
}

client.SetChallengeProvider(acme.DNS01, provider)
client.SetChallengeProvidersDNS(acme.DNS01, providers)

// --dns=foo indicates that the user specifically want to do a DNS challenge
// infer that the user also wants to exclude all other challenges
Expand Down