go-acme / lego

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

Support for selfhost #2277

Closed aifrog closed 2 months ago

aifrog commented 2 months ago

Welcome

How do you use lego?

Through Traefik

Link to the DNS provider

https://www.selfhost.de

Link to the API documentation

?

Additional Notes

There is an implementation inside acme.sh: https://github.com/acmesh-official/acme.sh/blob/master/dnsapi/dns_selfhost.sh

https://github.com/traefik/traefik/issues/11096

ldez commented 2 months ago

Hello,

can you contact selfhost to get their API documentation?

I need the API documentation:

aifrog commented 2 months ago

Does this help:

    1 #!/usr/bin/env sh
    2 # shellcheck disable=SC2034
    3 dns_selfhost_info='SelfHost.de
    4 Site: SelfHost.de
    5 Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_selfhost
    6 Options:
    7  SELFHOSTDNS_USERNAME Username
    8  SELFHOSTDNS_PASSWORD Password
    9  SELFHOSTDNS_MAP Subdomain name
   10 Issues: github.com/acmesh-official/acme.sh/issues/4291
   11 Author: Marvin Edeler
   12 '
   13 
   14 dns_selfhost_add() {
   15   fulldomain=$1
   16   txt=$2
   17   _info "Calling acme-dns on selfhost"
   18   _debug fulldomain "$fulldomain"
   19   _debug txtvalue "$txt"
   20 
   21   SELFHOSTDNS_UPDATE_URL="https://selfhost.de/cgi-bin/api.pl"
   22 
   23   # Get values, but don't save until we successfully validated
   24   SELFHOSTDNS_USERNAME="${SELFHOSTDNS_USERNAME:-$(_readaccountconf_mutable SELFHOSTDNS_USERNAME)}"
   25   SELFHOSTDNS_PASSWORD="${SELFHOSTDNS_PASSWORD:-$(_readaccountconf_mutable SELFHOSTDNS_PASSWORD)}"
   26   # These values are domain dependent, so read them from there
   27   SELFHOSTDNS_MAP="${SELFHOSTDNS_MAP:-$(_readdomainconf SELFHOSTDNS_MAP)}"
   28   # Selfhost api can't dynamically add TXT record,
   29   # so we have to store the last used RID of the domain to support a second RID for wildcard domains
   30   # (format: 'fulldomainA:lastRid fulldomainB:lastRid ...')
   31   SELFHOSTDNS_MAP_LAST_USED_INTERNAL=$(_readdomainconf SELFHOSTDNS_MAP_LAST_USED_INTERNAL)
   32 
   33   if [ -z "${SELFHOSTDNS_USERNAME:-}" ] || [ -z "${SELFHOSTDNS_PASSWORD:-}" ]; then
   34     _err "SELFHOSTDNS_USERNAME and SELFHOSTDNS_PASSWORD must be set"
   35     return 1
   36   fi
   37 
   38   # get the domain entry from SELFHOSTDNS_MAP
   39   # only match full domains (at the beginning of the string or with a leading whitespace),
   40   # e.g. don't match mytest.example.com or sub.test.example.com for test.example.com
   41   # if the domain is defined multiple times only the last occurance will be matched
   42   mapEntry=$(echo "$SELFHOSTDNS_MAP" | sed -n -E "s/(^|^.*[[:space:]])($fulldomain)(:[[:digit:]]+)([:]?[[:digit:]]*)(.*)/\2\3\4/p")
   43   _debug2 mapEntry "$mapEntry"
   44   if test -z "$mapEntry"; then
   45     _err "SELFHOSTDNS_MAP must contain the fulldomain incl. prefix and at least one RID"
   46     return 1
   47   fi
   48 
   49   # get the RIDs from the map entry
   50   rid1=$(echo "$mapEntry" | cut -d: -f2)
   51   rid2=$(echo "$mapEntry" | cut -d: -f3)
   52 
   53   # read last used rid domain
   54   lastUsedRidForDomainEntry=$(echo "$SELFHOSTDNS_MAP_LAST_USED_INTERNAL" | sed -n -E "s/(^|^.*[[:space:]])($fulldomain:[[:digit:]]+)(.*)/\2/p")
   55   _debug2 lastUsedRidForDomainEntry "$lastUsedRidForDomainEntry"
   56   lastUsedRidForDomain=$(echo "$lastUsedRidForDomainEntry" | cut -d: -f2)
   57 
   58   rid="$rid1"
   59   if [ "$lastUsedRidForDomain" = "$rid" ] && ! test -z "$rid2"; then
   60     rid="$rid2"
   61   fi
   62 
   63   _info "Trying to add $txt on selfhost for rid: $rid"
   64 
   65   data="?username=$SELFHOSTDNS_USERNAME&password=$SELFHOSTDNS_PASSWORD&rid=$rid&content=$txt"
   66   response="$(_get "$SELFHOSTDNS_UPDATE_URL$data")"
   67 
   68   if ! echo "$response" | grep "200 OK" >/dev/null; then
   69     _err "Invalid response of acme-dns for selfhost"
   70     return 1
   71   fi
   72 
   73   # write last used rid domain
   74   newLastUsedRidForDomainEntry="$fulldomain:$rid"
   75   if ! test -z "$lastUsedRidForDomainEntry"; then
   76     # replace last used rid entry for domain
   77     SELFHOSTDNS_MAP_LAST_USED_INTERNAL=$(echo "$SELFHOSTDNS_MAP_LAST_USED_INTERNAL" | sed -n -E "s/$lastUsedRidForDomainEntry/$newLastUsedRidForDomainEntry/p")
   78   else
   79     # add last used rid entry for domain
   80     if test -z "$SELFHOSTDNS_MAP_LAST_USED_INTERNAL"; then
   81       SELFHOSTDNS_MAP_LAST_USED_INTERNAL="$newLastUsedRidForDomainEntry"
   82     else
   83       SELFHOSTDNS_MAP_LAST_USED_INTERNAL="$SELFHOSTDNS_MAP_LAST_USED_INTERNAL $newLastUsedRidForDomainEntry"
   84     fi
   85   fi
   86 
   87   # Now that we know the values are good, save them
   88   _saveaccountconf_mutable SELFHOSTDNS_USERNAME "$SELFHOSTDNS_USERNAME"
   89   _saveaccountconf_mutable SELFHOSTDNS_PASSWORD "$SELFHOSTDNS_PASSWORD"
   90   # These values are domain dependent, so store them there
   91   _savedomainconf SELFHOSTDNS_MAP "$SELFHOSTDNS_MAP"
   92   _savedomainconf SELFHOSTDNS_MAP_LAST_USED_INTERNAL "$SELFHOSTDNS_MAP_LAST_USED_INTERNAL"
   93 }
   94 
   95 dns_selfhost_rm() {
   96   fulldomain=$1
   97   txt=$2
   98   _debug fulldomain "$fulldomain"
   99   _debug txtvalue "$txt"
  100   _info "Creating and removing of records is not supported by selfhost API, will not delete anything."
  101 }
ldez commented 2 months ago

Please don't do that.

The acme.sh script is not an API documentation.

Based on the information I was able to find:

So, can you contact SelfHost to ask them to expose a real API, and provide the documentation of this API.

aifrog commented 2 months ago

Sorry, wasn't aware of this. I will contact selfhost and ask for the API documention. Hope, that they have a good answer. Thx for your help.

ldez commented 2 months ago

I asked you to contact SelfHost because nothing will change unless their users request it.

The Lorax

I created an implementation, with the same constraint as the acme.sh implementation: you will need to create TXT records before using lego.

This will be "complex" to configure but this is how the endpoint works :shrug:

SelfHost really needs to expose a real API (and public documentation):

So please take a few minutes to get in touch with them, you can link this issue if needed.

aifrog commented 2 months ago

I got the following answer. Sorry, its in German. Let me know if I need to translate:

Guten tag,

da wir häufiger in Deutsch kommuniziert haben, hier das notwendigste:

ja solch eine API haben wir, Sie müssen vorher nur einen TXT-Record anlegen. Danach sehen Sie in den Details (Klick auf Ändern) direkt die ID

curl -X POST -F 'username=%USERNAME%' -F 'password=%PASSWORD%' -F 'rid=%RECORDID%' -F 'content=%CONTENT%' https://selfhost.de/cgi-bin/api.pl

bzw. hier die reine Update URL

https://selfhost.de/cgi-bin/api.pl?username=%USERNAME%&password=%PASSWORD%&rid=%RECORDID&content=%CONTENT%

Dies sind die POST & GET Möglichkeit, den Inhalt dieses TXT-Records dann zu ändern.

Beachten Sie: Für diese API ist der Benutzername und das Passwort des Logins, Nicht DynDNS Accounts, notwendig.

ldez commented 2 months ago

ja solch eine API haben wir, Sie müssen vorher nur einen TXT-Record anlegen. Danach sehen Sie in den Details (Klick auf Ändern) direkt die ID

This is not professional for SelfHost to answer that.

The endpoint, because it's just an undocumented endpoint and not a real API, is only to update an existing record. This endpoint is not what all ACME clients expect. An ACME client, like lego, expects an API to handle the creation and deletion of records, not to update existing records by using IDs grabbed manually from the UI.

So, could you try to explain that to SelfHost? Don't hesitate to send them this issue.

In all cases, I created PR #2278 as explained in my previous message, can you try it?

aifrog commented 2 months ago

I will try, however I am traveling right now and it will take some time. I also explained to selfhost and will let you know their feedback. Thx!

aifrog commented 2 months ago

Feedback from Selfhost, did my best - and will test your solution as soon as possible:

Das wurde bisher von allen Stellen so akzteptiert und... ob legal oder illegal (offiziell gilt diese Schnittstelle nur als Beta und wurde nicht veröffentlich, aber eine Kunden agierten anders) so eingebaut wenn es möglich war.

Wenn die sich dagegen streuben, eine API einzubinden, die den _acme-challenge TXT-Record anpassen der für die DNS-Authentifizierung für Let's Encrypt einwandfrei bisher funktionierte, dann tut es uns leid. Aber wir können keine Api mit erstellung udn Löschung einer DNS allein dienen, da dies eine Gefährdung der DNS selbst ist.

Also kurz um: nein. Unsere Lösung hat bisher jeden zufrieden gestellt, hier können wir nichts weiter tun.

ldez commented 2 months ago

What a joke...

Das wurde bisher von allen Stellen so akzteptiert und... ... Unsere Lösung hat bisher jeden zufrieden gestellt, hier können wir nichts weiter tun.

Their API doesn't satisfy anyone, we just use what we have, because they don't give any other choice.

Aber wir können keine Api mit erstellung udn Löschung einer DNS allein dienen, da dies eine Gefährdung der DNS selbst ist.

lego has around 140 DNS providers, so I think I know very well the API of DNS providers, and all the DNS providers in the world provide an API to add and delete records, there is no threat against the DNS itself.

ob legal oder illegal (offiziell gilt diese Schnittstelle nur als Beta und wurde nicht veröffentlich, aber eine Kunden agierten anders) so eingebaut wenn es möglich war.

It's a shame to say that.

  1. Inside the first email, they didn't talk about "beta"/"unpublished" API or any limitation of the endpoint usage.
  2. They are trying to put shame on customers to use what they said...

So, SelfHost, if you are reading this issue, please improve your API.

ldez commented 2 months ago

@aifrog I hope you enjoyed my work, please consider donating or asking your company to do so. This will be appreciated, thank you :heart:

https://github.com/sponsors/ldez