Repository: certbot/certbot

Issue:
Issue renewing subdomains on certbot+certbot-dns-dnsmadeeasy v2.7.0+
I strongly believe this was an issue introduced in 2.7.0.  I managed to get v2.6.3 reinstalled using a Python VENV and it worked fine again.  You can find more details/discussion about this issue here:
https://community.letsencrypt.org/t/dnsmadeeasy-certbot-plugin-error-unable-to-determine-zone-identifier-for-domain-when-using-subdomains/206963

The short version of this issue is that on version 2.7.0+, the calls to the DNSMadeEasy API are improperly constructed to omit the subdomains from the URLs.  Given the hypothetical domain `subdomain-two.subdomain-one.example.com`:

Version 2.6.3 and lower queries this set of URLS until it finds a match:
```
https://api.dnsmadeeasy.com/V2.0/dns/managed/name?domainname=subdomain-two.subdomain-one.example.com
https://api.dnsmadeeasy.com/V2.0/dns/managed/name?domainname=subdomain-one.example.com
https://api.dnsmadeeasy.com/V2.0/dns/managed/name?domainname=example.com
https://api.dnsmadeeasy.com/V2.0/dns/managed/name?domainname=.com
```

However, version 2.7.0+ incorrectly queries this set of URLs and never finds a match:
```
https://api.dnsmadeeasy.com/V2.0/dns/managed/name?domainname=example.com
https://api.dnsmadeeasy.com/V2.0/dns/managed/name?domainname=example.com
https://api.dnsmadeeasy.com/V2.0/dns/managed/name?domainname=example.com
https://api.dnsmadeeasy.com/V2.0/dns/managed/name?domainname=.com
```

For whatever reason you can see it's stripping off the subdomains in v2.7.0+.  We only have the `subdomain-one` level (from the example) managed within DNSMadeEasy so the version 2.7.0+ behavior will never find a match.  I know we're probably a bit of an edge-case, but I doubt we're the only ones in this boat.

## My operating system is (include version):
Ubuntu 16.04.7 LTS (Yeah, I know it's old ... we're working on that.)

## I installed Certbot with (snap, OS package manager, pip, certbot-auto, etc):
snap

## I ran this command and it produced this output:
```
ᐅ sudo certbot renew --verbose --cert-name subdomain-two.subdomain-one.example.com
Saving debug log to /var/log/letsencrypt/letsencrypt.log

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Processing /etc/letsencrypt/renewal/subdomain-two.subdomain-one.example.com.conf
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Certificate is due for renewal, auto-renewing...
Plugins selected: Authenticator dns-dnsmadeeasy, Installer nginx
Renewing an existing certificate for *.subdomain-two.subdomain-one.example.com
Performing the following challenges:
dns-01 challenge for subdomain-two.subdomain-one.example.com
Cleaning up challenges
Failed to renew certificate subdomain-two.subdomain-one.example.com with error: Unable to determine zone identifier for subdomain-two.subdomain-one.example.com using zone names: ['subdomain-two.subdomain-one.example.com', 'subdomain-one.example.com', 'example.com', 'com']

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
All renewals failed. The following certificates could not be renewed:
  /etc/letsencrypt/live/subdomain-two.subdomain-one.example.com/fullchain.pem (failure)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1 renew failure(s), 0 parse failure(s)
Ask for help or search for solutions at https://community.letsencrypt.org. See the logfile /var/log/letsencrypt/letsencrypt.log or re-run Certbot with -v for more details.
```

## Certbot's behavior differed from what I expected because:
The cert should be renewed as it did with versions of certbot+certbot-dns-dnsmadeeasy prior to version 2.7.0.

## Here is a Certbot log showing the issue (if available):
###### Logs are stored in `/var/log/letsencrypt` by default. Feel free to redact domains, e-mail and IP addresses as you see fit.
```
DEBUG:urllib3.connectionpool:https://api.dnsmadeeasy.com:443 "GET /V2.0/dns/managed/name?domainname=example.com HTTP/1.1" 404 None
DEBUG:urllib3.connectionpool:https://api.dnsmadeeasy.com:443 "GET /V2.0/dns/managed/name?domainname=example.com HTTP/1.1" 404 None
DEBUG:urllib3.connectionpool:https://api.dnsmadeeasy.com:443 "GET /V2.0/dns/managed/name?domainname=example.com HTTP/1.1" 404 None
DEBUG:urllib3.connectionpool:https://api.dnsmadeeasy.com:443 "GET /V2.0/dns/managed/name?domainname=.com HTTP/1.1" 404 None
```

## Here is the relevant nginx server block or Apache virtualhost for the domain I am configuring:
N/A


Discussion:
Thanks so much for the detailed bug report.

This to me looks related to https://github.com/certbot/certbot/issues/9791 where someone had success setting the environment variable `LEXICON_DELEGATED` to the name of your DNS zone and then running Certbot which you may want to try. See https://github.com/certbot/certbot/issues/9791#issuecomment-1768548614.

Even if that works, that's just a workaround though and I'll look into actually getting this fixed as soon as I can.

Resolving patch:
diff --git a/certbot/certbot/plugins/dns_common_lexicon.py b/certbot/certbot/plugins/dns_common_lexicon.py
--- a/certbot/certbot/plugins/dns_common_lexicon.py
+++ b/certbot/certbot/plugins/dns_common_lexicon.py
@@ -198,6 +198,10 @@ def _build_lexicon_config(self, domain: str) -> ConfigResolver:
 
         dict_config = {
             'domain': domain,
+            # We bypass Lexicon subdomain resolution by setting the 'delegated' field in the config
+            # to the value of the 'domain' field itself. Here we consider that the domain passed to
+            # _build_lexicon_config() is already the exact subdomain of the actual DNS zone to use.
+            'delegated': domain,
             'provider_name': self._provider_name,
             'ttl': self._ttl,
             self._provider_name: {item[2]: self._credentials.conf(item[0])


Test patch: