Skip to content

Overriding the default challenge domain in DNS authenticators#5350

Closed
quinot wants to merge 7 commits intocertbot:masterfrom
quinot:topic/dns-follow-cnames
Closed

Overriding the default challenge domain in DNS authenticators#5350
quinot wants to merge 7 commits intocertbot:masterfrom
quinot:topic/dns-follow-cnames

Conversation

@quinot
Copy link
Copy Markdown

@quinot quinot commented Dec 25, 2017

Support a new option --dns--follow-cnames whereby
certbot will follow a (chain of) CNAME record(s) at the
challenge domain (_acme-challenge.) and instead
update the target domain.

Independently of this option, this change also incidentally fixes
the possibly uncommon use case where _acme-challenge is itself a
delegation apex (previously the RFC2136 plugin would generate an
update for the wrong zone in that case).

@joohoi
Copy link
Copy Markdown
Member

joohoi commented Jan 4, 2018

Thanks for the PR!

I see this as an important addition to the existing functionality of DNS plugins, and we definitely will need to handle it.

I read through the PR code, and there's a few points I would like to raise for discussion:

  • I can see how this could cause confusion in user, and/or plugin behavior, as the CNAME can and often will point to a different DNS server and/or API. eg. using Route 53 plugin as the zone root exists in Route 53, but the CNAME points to a Cloudflare instance. Or to a same API, but with different credentials.
  • This is functionality that's tailored towards more advanced configuration, and adding complexity in form of CNAME chain resolution seems like something that we could omit here. The user should know where the CNAME points to, and what to update.
  • The PR adds a new dependency (dnspython), and we're trying to trim down dependencies as much as possible, as they cause a lot of extra work for distribution package maintainers.

That said, I really would love to see the functionality in our range of DNS plugins as it is an important and effective way to avoid storing credentials to the actual DNS zone in the boxes themselves.

I would propose following changes to the PR:

  • Removing the autodetection code (CNAME chain resolving), and hence avoid adding the new dependency.
  • Making the new CLI flag something along the lines of:
    --dns-force-record=where.cname.points.to.tld, which would simply be the actual record that the plugin needs to update instead of the one derived from -d yourdomain.tld

These changes would make it clear from the get go which plugin, and which credentials need to be used in addition of avoiding adding complexity and a new dependency.

Let me know what you think!

@quinot
Copy link
Copy Markdown
Author

quinot commented Jan 4, 2018

Hi @joohoi , thanks for your feedback!

Simply overriding the target name was an option I did consider. I opted for doing CNAME chain resolution instead out of a DRY consideration: requiring the user to provide the target of the CNAME repeats some information we know already exists in the DNS. Also, one thing to keep in mind is that we need to handle the case of multiple subjectaltnames being requested: in that case you have different target names, so the command line option to override the challenge domain needs to be per-subjectaltname (just like -w is for webroot).

On the other hand, I do see the merit of not dragging in an additional dependency.

So, I'll send in a modified version:

  • removing the CNAME chain resolution
  • adding a new option allowing to set a per-domain override for the challenge domain
  • in order to make it more convenient and limit the amount of stuff that needs to be repeated, I'll make it a format string so that you can say:

--dns-challenge-domain "{domain}.mychallenges.example.com" -d domain1.example.com -d domain2.example.com

instead of having to write:

--dns-challenge-domain domain1.example.com.mychallenges.example.com -d domain1.example.com --dns-challenge-domain domain2.example.com.mychallenges.example.com -d domain2.example.com

(However I think the second, more verbose form needs to be supported anyway, so I'll steal some code from webroot -- I'll try to see if some of it can be shared).

@joohoi
Copy link
Copy Markdown
Member

joohoi commented Jan 5, 2018

I look forward to the changes. The proposed convenience functionality of variable record part seems like a good idea where multiple SAN domains are needed. And there definitely is a need for the webroot style mapping as well, good catch!

@quinot
Copy link
Copy Markdown
Author

quinot commented Jan 14, 2018

@joohoi The new code is here, with CI passing! I had to tweak the testsuite a bit in order to avoid having to repeat configuration options for every DNS plugin. Common options are now cleanly set in a single place.

@quinot quinot changed the title Support following CNAMEs in DNS plugins Overriding the default challenge domain in DNS authenticators Jan 15, 2018
@joohoi
Copy link
Copy Markdown
Member

joohoi commented Jan 16, 2018

Thanks! We're currently busy with mitigating the removal of TLS-SNI, but I'll try to get back to review this PR as soon as possible.

@quinot
Copy link
Copy Markdown
Author

quinot commented Jan 18, 2018

Understood, thanks for the heads-up, and good luck with the SNI issue!

@bmw bmw added this to the 0.22.0 milestone Jan 26, 2018
@quinot quinot mentioned this pull request Feb 3, 2018
Copy link
Copy Markdown
Member

@joohoi joohoi left a comment

Choose a reason for hiding this comment

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

I'm terribly sorry it took so long to get back to reviewing this important pull request but here we go. I really like the new design and the general code quality is excellent. Thank you for working on this!

However there's a few required changes that are blocking the merge currently. The most important of course is the current state of being unable to use the DNS plugins without the override challenges CLI flag.

domain = achall.domain
acme_loc = achall.validation_domain_name(achall.domain)

challenge_map = self.conf('override-challenge-map')
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

In the current state Certbot crashes when run without the override parameter.

If the --dns-###-override-challenge parameter isn't provided on the commandline, these values do not get initialized in the configuration, and Certbot will error out here.

I would like to see (at least some) test coverage for this case too.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Ah, indeed an oversight.
Test coverage here will be interesting, as currently all DNS plugin tests provide a fake config which is assumed to reflect the actual namespace from argument parsing, and here what we'd want is a test based on the actual argument parser.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Sorry, I should have been more clear here. Mocking or changing the test config object is completely fine for this purpose. The argument parsing is done and tested elsewhere in the project. What I'm interested here is testing the inner plumbing with and without the parsed configuration variables present to prevent regressions in the future.

'location. The specified value may include "{domain}" '
'and "{acme}" format strings, which will respectively '
'expand to the domain being requested, and the corresponding '
'standard ACME challenge location.')
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The otherwise clear documentation is a bit fuzzy about the definition of {acme} format string. I think this should be clarified.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

OK, will try... {acme} is the default location, i.e. if domain is "example.com", {acme} is "_acme-challenge.example.com".

for domain in namespace.domains:
self.challenge_map.setdefault(domain,
getattr(namespace, self.dest))
setattr(namespace, self.dest, override_chall)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This probably should happen before the for loop above, as all the values in self.challenge_map are mapped to the default value of the parameter ({acme}) if not. This results to every domain getting the default value as an override domain.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

This part was actually as intended. The override option is intended to apply to domains that appear after it on the command line, so the loop here applies the previous value to previous domains. This is similar to what the webroot plugin does.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

(The new commit pushed today includes additional comments here to make the flow clearer).

@quinot
Copy link
Copy Markdown
Author

quinot commented Feb 10, 2018

I have pushed a new commit that fixes the case of no override specified. I have adjusted the testsuite as mentioned in my earlier comment, so that both this case, and the case of override specifieds, are tested with the actual argument parsing circuitry, instead of a mock configuration. I also had successful certificate issuance dry-runs, both with and without override specified.

Copy link
Copy Markdown
Member

@joohoi joohoi left a comment

Choose a reason for hiding this comment

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

Unfortunately, the way that most of the current Certbot DNS plugins work is that they resolve the zone root using the domain variable passed to _perform() function as a base to resolve the zone root to. This results into following scenario:

User wants to get certificate for actualdomain.tld but uses records under validationdomain.tld for the DNS validation by pointing CNAME record _acme-challenge.actualdomain.tld to actual.validationdomain.tld.

  • Certbot CLI certbot certonly -a dns-cloudflare --dns-cloudflare-override-challenge actual.validationdomain.tld -d actualdomain.tld
  • _perform() gets arguments: domain=actualdomain.tld validation_name=actual.validationdomain.tld validation=...
  • Value of domain gets used as zone root, and validation_name will be used as the record name.

As a result, a following record would be created:

actual.validationdomain.tld.actualdomain.tld    TXT    "..."

self._attempt_cleanup = False

@classmethod
def inject_parser_options(cls, parser, name):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I would like to see a short docstring here to explain the need for kwargs expansion etc. This is to make the functionality clear for someone reading the code in a year from now.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

(for the record, this has been done)

@quinot
Copy link
Copy Markdown
Author

quinot commented Feb 16, 2018

Hmmm right indeed. As a matter of fact, the one I was able to extensively test (RFC 2136) did have that issue. I looked for it in other plugins, but missed the fact that the shared Lexicon module had it too. So, let's see:

  • Lexicon-based ones are now OK (cloudxns, dnsimple, dnsmadeeasy, luadns, nsone)
  • Digitalocean, Google, Cloudflare are now OK
  • RFC 2136 was OK with my first commit
  • Route53 was already OK

@joohoi
Copy link
Copy Markdown
Member

joohoi commented Feb 17, 2018

I have a few thoughts regarding the arguments passed to the actual DNS plugin method _perform() from the plugins/dns_common.py, namely domain, validation_domain and validation.

I feel that a solution where the domain value passed to the actual DNS plugin would have the overriding domain would be much cleaner. This would also allow possible third party DNS plugins that are using our DNS plugin API to benefit from this change automatically.

When the validation domain is overridden by the newly added CLI argument, I don't think there would be any reason for the actual executor part (the DNS plugin) to handle the original domain if it's explicitly overridden. I might be missing something, but wasn't able to come up with an situation where this wouldn't be the case.

How would you feel to change the behavior as such?

@quinot
Copy link
Copy Markdown
Author

quinot commented Feb 18, 2018

If you suggestion is to remove the original domain for the arguments of _perform, and keep only the validation (challenge) domain, yes, that makes sense; as a matter of fact that's the direction I've been taking, already removing the original domain parameter from the various {add,del}_txt_record functions. I can go one step further and also remove it from _perform, that will be a good cleanup.

@joohoi
Copy link
Copy Markdown
Member

joohoi commented Feb 19, 2018

If you suggestion is to remove the original domain for the arguments of _perform, and keep only the validation (challenge) domain, yes, that makes sense; as a matter of fact that's the direction I've been taking, already removing the original domain parameter from the various {add,del}_txt_record functions. I can go one step further and also remove it from _perform, that will be a good cleanup.

I'm afraid we can't do that in order to keep the API intact with the external DNS plugins. But what I think we should do is to override the value with domain from --dns-<plugin>-override-challenge.

@quinot
Copy link
Copy Markdown
Author

quinot commented Feb 24, 2018

I see your point with not changing the API for external plugins. So what you suggest is actually passing the same value (the challenge domain, possibly overridden) for both parameters of _perform, right?

@joohoi
Copy link
Copy Markdown
Member

joohoi commented Feb 25, 2018

I see your point with not changing the API for external plugins. So what you suggest is actually passing the same value (the challenge domain, possibly overridden) for both parameters of _perform, right?

Correct! If you see some issues in this approach, please let me know and we can figure a better way to handle this. Currently I think that this would be most sane way to handle the issue at hand.

@quinot
Copy link
Copy Markdown
Author

quinot commented Feb 26, 2018

OK, good, that's what I implemented in the last commit (80a6754).

@bmw bmw modified the milestones: 0.22.0, 0.23.0 Mar 7, 2018
@quinot
Copy link
Copy Markdown
Author

quinot commented Mar 7, 2018

Change rebased to resolve conflicts with latest changes for Google DNS.

@joohoi
Copy link
Copy Markdown
Member

joohoi commented Mar 12, 2018

I think that leaving the actual plugin (executor) code like it is currently would be the best way forward, as when not overriding the domain, it will save a few queries for finding the domain root. When the domain is overridden, the plugins would then just use the (also overridden) domain value for the SOA resolution.

@quinot
Copy link
Copy Markdown
Author

quinot commented Mar 20, 2018

Hi @joohoi, sorry for the delay in getting back to you, I was on vacation.
There's one thing to be careful of with keeping the old behaviour unchanged when the name is not overridden: it is incorrect anytime _acme-challenge. is itself a delegation apex. Always resolving from that name (and not from its parent by default) ensures that this corner case is handled correctly as well.

@bmw bmw modified the milestones: 0.23.0, 0.24.0 Mar 28, 2018
@quinot
Copy link
Copy Markdown
Author

quinot commented Apr 5, 2018

Hi @joohoi, where do we stand with the current situation? As I said, restoring the previous behaviour of starting the lookup at the enclosing domain in the case where it's not overridden will save a few queries, but will reintroduce a bug whereby the case where _acme-challenge is itself a delegation apex is not handled correctly.

@joohoi
Copy link
Copy Markdown
Member

joohoi commented Apr 5, 2018

Yeah! You are absolutely correct, and this was addressed separately in another PR for rfc2136 plugin. The review process is unfortunately taking a while, as we have to go through all the different providers functionality separately. Sorry for the delay, but this is being worked on!

@quinot
Copy link
Copy Markdown
Author

quinot commented Sep 1, 2018

Good catch! Route53 plugin fixed.

@bmw bmw self-assigned this Sep 12, 2018
@bmw
Copy link
Copy Markdown
Member

bmw commented Sep 12, 2018

@joohoi, can you give an update on the status here? I don't think you need to do the work, but I'd like to know what still needs to be done so someone else can take over.

@bmw bmw modified the milestones: 0.27.0, 0.28.0 Sep 13, 2018
@jkkn
Copy link
Copy Markdown

jkkn commented Sep 28, 2018

@joohoi @bmw any chance of this getting merged anytime soon?

@joohoi
Copy link
Copy Markdown
Member

joohoi commented Sep 28, 2018

I have tested it against all but Google DNS service, and the few that we don't have credentials for. Everything should be in order regarding the functionality. I still have some concerns regarding the CLI help text, and feel that it should be clarified a bit, but am unsure how to actually do this while keeping the help text short enough.

@quinot
Copy link
Copy Markdown
Author

quinot commented Sep 28, 2018

One possible clarification would be to add to the help text:

For the typical use case where you want all of your challenges under a
single dynamic zone "dyn-zone.example.com", you would thus specify
"{domain}.dyn-zone.example.com".

I'll be happy to take care of it if you think this addition would be useful.

@quinot
Copy link
Copy Markdown
Author

quinot commented Oct 5, 2018

@joohoi ping? Shall I update the help text as indicated Is there anything else still blocking merge?

@siwyd
Copy link
Copy Markdown

siwyd commented Oct 5, 2018

I would definitely appreciate this getting merged! I've got a need for this exact use case in the very near future :)

@mweinelt
Copy link
Copy Markdown

mweinelt commented Oct 5, 2018

Following CNAMEs for DNS challenges is what's blocking our IRC network (hackint.org) from migrating to Letsencrypt at this point, so additional priority given to this would be highly appreciated.

@jkkn
Copy link
Copy Markdown

jkkn commented Oct 12, 2018

Nearly an year later... will this merge ever happen? @joohoi

@quinot
Copy link
Copy Markdown
Author

quinot commented Oct 23, 2018

Still no feedback more than three weeks after my last message here. I do not have the bandwidth to maintain a fork of certbot in the long term, so I will be looking into other ACME clients. I find it quite unfortunate that we were not able to have this useful feature incorporated in the "official" one.

@bmw
Copy link
Copy Markdown
Member

bmw commented Oct 23, 2018

I'm sorry you haven't gotten a response here. Both joohoi and I have been on personal leave. While we often are unable to immediately review PRs (as Certbot has only 1 full time developer right now), we certainly try to avoid people getting no response for weeks at a time.

We appreciate this contribution and plan to look at it in the coming weeks. In the meantime, if another client works better for you, please use it! The important thing is you're able to get a cert for your site.

@bmw bmw modified the milestones: 0.28.0, 0.29.0 Nov 7, 2018
@jkkn
Copy link
Copy Markdown

jkkn commented Nov 15, 2018

@joohoi / @bmw - any news on merging this?

@bmw
Copy link
Copy Markdown
Member

bmw commented Nov 15, 2018

Unfortunately no. It's still on our radar, but we haven't had time to get to it yet. As far as I know, the biggest issue here is manually testing the changes here against all affected providers.

@quinot quinot closed this Dec 3, 2018
@bmw
Copy link
Copy Markdown
Member

bmw commented Dec 3, 2018

@quinot, are you OK with us moving your code here into a branch on our repo and building off of it and testing it ourselves when we finally have cycles to work on this?

@quinot
Copy link
Copy Markdown
Author

quinot commented Dec 6, 2018

@bmw feel free to.

@bmw
Copy link
Copy Markdown
Member

bmw commented Dec 6, 2018

Done. I created #6566 and put your work in the quinot/topic/dns-follow-cnames branch.

Thanks again for your help @quinot.

@Maharacha
Copy link
Copy Markdown

Is the functionality to use an alias for the dns challenge merged to master? Or do I need to use the branch https://github.com/certbot/certbot/tree/quinot/topic/dns-follow-cnames to have it?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants