go-acme / lego

Let's Encrypt/ACME client and library written in Go
https://go-acme.github.io/lego/
MIT License
8.01k stars 1.02k forks source link

GoDaddy API Limitations #2269

Closed kanwhoa closed 2 months ago

kanwhoa commented 2 months ago

Welcome

What did you expect to see?

The GoDaddy DNS provider cleans up old record of type TXT with name _acme-challenge.

Note that I understand this is not a lego fault, it's a change with the API access allowed by GoDaddy. However, after some playing, I found that the APIs which could be used for ACME are still open.

For example, the following request is allowed to delete:

curl --verbose \
  -X DELETE \
  -H "Authorization: sso-key x:y" \
  -d "" \
  "https://api.godaddy.com/v1/domains/domain.tld/records/TXT/_acme-challenge.infra"

So, there's likely some API used which is disallowed by the new policies put in place by GoDaddy. However, there seems to be some workarounds. Btw, the TXT record creation works without an issue.

What did you see instead?

The command line reported

failed to get all TXT records: unexpected status code: [status code: 403] body: {"code":"ACCESS_DENIED","message":"Authenticated user is not allowed access"

How do you use lego?

Binary

Reproduction steps

  1. Issue the command
    GODADDY_API_KEY=x GODADDY_API_SECRET=y lego \
    --domains "subdomain.domain.tld" \
    --accept-tos --email "certificates@domain.tld" \
    --dns godaddy \
    --dns.resolvers "ns17.domaincontrol.com,ns18.domaincontrol.com" \
    --dns-timeout 600 \
    run

Version of lego

lego version 4.18.0 darwin/arm64

Logs

```console 2024/09/10 14:19:22 [INFO] [subdomain.domain.tld] acme: Obtaining bundled SAN certificate 2024/09/10 14:19:23 [INFO] [subdomain.domain.tld] AuthURL: https://acme-v02.api.letsencrypt.org/acme/authz-v3/XXXXXXXXXXXXX 2024/09/10 14:19:23 [INFO] [subdomain.domain.tld] acme: Could not find solver for: tls-alpn-01 2024/09/10 14:19:23 [INFO] [subdomain.domain.tld] acme: Could not find solver for: http-01 2024/09/10 14:19:23 [INFO] [subdomain.domain.tld] acme: use dns-01 solver 2024/09/10 14:19:23 [INFO] [subdomain.domain.tld] acme: Preparing to solve DNS-01 2024/09/10 14:19:25 [INFO] [subdomain.domain.tld] acme: Trying to solve DNS-01 2024/09/10 14:19:25 [INFO] [subdomain.domain.tld] acme: Checking DNS record propagation. [nameservers=ns17.domaincontrol.com:53,ns18.domaincontrol.com:53] 2024/09/10 14:19:27 [INFO] Wait for propagation [timeout: 2m0s, interval: 2s] 2024/09/10 14:19:27 [INFO] [subdomain.domain.tld] acme: Waiting for DNS record propagation. 2024/09/10 14:19:30 [INFO] [subdomain.domain.tld] acme: Waiting for DNS record propagation. 2024/09/10 14:19:32 [INFO] [subdomain.domain.tld] acme: Waiting for DNS record propagation. 2024/09/10 14:19:34 [INFO] [subdomain.domain.tld] acme: Waiting for DNS record propagation. 2024/09/10 14:19:36 [INFO] [subdomain.domain.tld] acme: Waiting for DNS record propagation. 2024/09/10 14:19:39 [INFO] [subdomain.domain.tld] acme: Waiting for DNS record propagation. 2024/09/10 14:19:41 [INFO] [subdomain.domain.tld] acme: Waiting for DNS record propagation. 2024/09/10 14:19:43 [INFO] [subdomain.domain.tld] acme: Waiting for DNS record propagation. 2024/09/10 14:19:50 [INFO] [subdomain.domain.tld] acme: Cleaning DNS-01 challenge 2024/09/10 14:19:51 [WARN] [subdomain.domain.tld] acme: cleaning up failed: godaddy: failed to get all TXT records: unexpected status code: [status code: 403] body: {"code":"ACCESS_DENIED","message":"Authenticated user is not allowed access"} 2024/09/10 14:19:51 [INFO] Deactivating auth: https://acme-v02.api.letsencrypt.org/acme/authz-v3/XXXXXXXXXXXXX 2024/09/10 14:19:51 Could not obtain certificates: error: one or more domains had a problem: ```

Go environment (if applicable)

```console $ go version && go env # paste output here ```
ldez commented 2 months ago

https://go-acme.github.io/lego/dns/godaddy/index.html#additional-configuration

GoDaddy has recently (2024-04) updated the account requirements to access parts of their production Domains API:

Availability API: Limited to accounts with 50 or more domains. Management and DNS APIs: Limited to accounts with 10 or more domains and/or an active Discount Domain Club plan.

https://community.letsencrypt.org/t/getting-unauthorized-url-error-while-trying-to-get-cert-for-subdomains/217329/12

Related to #2182

ldez commented 2 months ago

FYI, your suggested endpoint is already the endpoint that lego uses.

But, this endpoint is not enough to handle the DNS challenge: the endpoint only allows handling all TXT records for a domain, which means that lego needs to get all the existing TXT records else all existing TXT records (even those nonrelated to the ACME challenge) will be deleted.

ldez commented 2 months ago

You should consider replacing GoDaddy with a DNS provider with a better policy.

If you already own a domain in another DNS provider, you can use the CNAME approach, like that you will be able to still use your GoDaddy domain.

Sadly I have no other solution, the GoDaddy API policies are extremely bad.

kanwhoa commented 2 months ago

Hi,

I should have noted in my original, that the GET URL also appears to work without restriction:

curl --verbose \
  -X GET \
  -H "Authorization: sso-key x:y" \
  -d "" \
  "https://api.godaddy.com/v1/domains/domain.tld/records/TXT/_acme-challenge.infra"

This should allow getting all TXT record for a domain or subdomain. I'm not sure where the "Authenticated user is not allowed access" message is being generated from, the log line just before indicate that it is cleaning up. I'm not sure of the particular API being called at this point though.

2024/09/10 19:19:15 [INFO] [*.subdomain.domain.tld] acme: Cleaning DNS-01 challenge
2024/09/10 19:19:15 [WARN] [*.subdomain.domain.tld] acme: cleaning up failed: godaddy: failed to get all TXT records: unexpected status code: [status code: 403] body: {"code":"ACCESS_DENIED","message":"Authenticated user is not allowed access"}
ldez commented 2 months ago

Sorry, I mixed the 2 endpoints in my head but the problem is the same:

lego uses:

As there are no alternatives to add a TXT record then there is no solution.

ldez commented 2 months ago

After re-checking the doc, there is another endpoint https://developer.godaddy.com/doc/endpoint/domains#/v1/recordAdd

But I guess the limitations are the same as the PUT endpoint.

ldez commented 2 months ago

Also, it seems (from a post on a forum) like the API limitations are not the same for the apex (example.com/_acme-challenge.example.com) or a subdomain (foo.example.com/_acme-challenge.foo.example.com)

But I think this doesn't really change the possibilities.

kanwhoa commented 2 months ago

let me re-clarify here:

Command Using LEGO Using curl
Add TXT record Working, no issue. Working, no issue.
Get TXT record ? Working, no issue.
Delete TXT record Not working - API limitation issue Working, no issue

I do get the issue with GoDaddy, and would encourage every user to make a formal complaint for a basic API access. However, it will be a commercial decision and so unlikely to change. That said, there appears to be a route to allow the Lego provider to work, but it seems like one particular API is being called which triggers the issue.

If there's a way to get HTTP trace logging out of Lego, I'm more than happy to debug, and locate the specific API being called that causes the issue.

ldez commented 2 months ago

The lego implementation for GoDaddy is basic:

It's the same call to add and "delete".

So nothing special.

failed to get all TXT records: unexpected status code: [status code: 403] body: {"code":"ACCESS_DENIED","message":"Authenticated user is not allowed access"

The error message is clear: it fails during the clean up when lego gets all domains.

The workflow is this one:

ldez commented 2 months ago

GoDaddy limitations are extreme: one call can work but 2 calls in a row are not working.

Clearly, you should go away from this DNS provider or use the CNAME approach with another DNS provider.

kanwhoa commented 2 months ago

Found the issue.

The error in question is generated from the godaddy.go:177. The final argument to this call is a blank string. I presume at some point, this meant all records of type. This is passed to client.go:40 where it is placed into the request.

Replicating the call in curl gets the same API error:

curl \
  -X GET \
  -H "Authorization: sso-key x:y" \
  "https://api.godaddy.com/v1/domains/domain.tld/records/TXT/"
{"code":"ACCESS_DENIED","message":"Authenticated user is not allowed access"}

However, placing "_acme-challenge." in the final argument works correctly. 100% of the time.

e.g.

curl \
  -X GET \
  -H "Authorization: sso-key x:y" \
  "https://api.godaddy.com/v1/domains/domain.tld/records/TXT/_acme-challenge.subdomain"
[...]

The "subdomain" part should be the domain(s) between the root of the owned domain in the GoDaddy console and the record. For example requesting "a.b.c.example.com" and "example.com" is the owned domain, then the last part of the request should be "_acme-challenge.a.b.c"

ldez commented 2 months ago

the subdomain should not be empty because it's a subtraction of the auth zone (example.com) and the effective FQDN (_acme-challenge.foo.example.com.) -> _acme-challenge.foo

So I don't understand why you think that subdomain is empty.

ldez commented 2 months ago

Also the "present" and the "cleanup" use the exact same code, so it doesn't make sense to have a different behavior between them.

The only way to get an empty subdomain is to have the auth zone equal to effective FQDN, which is impossible.

ldez commented 2 months ago

oh I see

ldez commented 2 months ago

Can you try https://github.com/go-acme/lego/pull/2270 ?

ldez commented 2 months ago

In fact, there are some API changes because this implementation has been used since 2020 without problems.

Good catch :+1:

I modified the implementation to follow your findings and also implemented the DELETE.

I will wait for your feedback about #2270.

kanwhoa commented 2 months ago

Tested. There were some latent issues with the API. I've fixed and attached a diff. Sorry, was having trouble updating the PR. godaddyfix.diff.patch

ldez commented 2 months ago

Most of your patch was already inside the PR, so I just kept the effective diff.

d424d2d09d11b604b65da74db625e7a2d617509d