Skip to content

Commit

Permalink
Add DNS provider for SelfHost.(de|eu) (#2278)
Browse files Browse the repository at this point in the history
Co-authored-by: Dominik Menke <git@dmke.org>
  • Loading branch information
ldez and dmke authored Sep 20, 2024
1 parent eb7de2a commit 20c8d6c
Show file tree
Hide file tree
Showing 13 changed files with 1,018 additions and 8 deletions.
15 changes: 8 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,13 +80,14 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns).
| [Open Telekom Cloud](https://go-acme.github.io/lego/dns/otc/) | [Oracle Cloud](https://go-acme.github.io/lego/dns/oraclecloud/) | [OVH](https://go-acme.github.io/lego/dns/ovh/) | [plesk.com](https://go-acme.github.io/lego/dns/plesk/) |
| [Porkbun](https://go-acme.github.io/lego/dns/porkbun/) | [PowerDNS](https://go-acme.github.io/lego/dns/pdns/) | [Rackspace](https://go-acme.github.io/lego/dns/rackspace/) | [RcodeZero](https://go-acme.github.io/lego/dns/rcodezero/) |
| [reg.ru](https://go-acme.github.io/lego/dns/regru/) | [RFC2136](https://go-acme.github.io/lego/dns/rfc2136/) | [RimuHosting](https://go-acme.github.io/lego/dns/rimuhosting/) | [Sakura Cloud](https://go-acme.github.io/lego/dns/sakuracloud/) |
| [Scaleway](https://go-acme.github.io/lego/dns/scaleway/) | [Selectel v2](https://go-acme.github.io/lego/dns/selectelv2/) | [Selectel](https://go-acme.github.io/lego/dns/selectel/) | [Servercow](https://go-acme.github.io/lego/dns/servercow/) |
| [Shellrent](https://go-acme.github.io/lego/dns/shellrent/) | [Simply.com](https://go-acme.github.io/lego/dns/simply/) | [Sonic](https://go-acme.github.io/lego/dns/sonic/) | [Stackpath](https://go-acme.github.io/lego/dns/stackpath/) |
| [Tencent Cloud DNS](https://go-acme.github.io/lego/dns/tencentcloud/) | [TransIP](https://go-acme.github.io/lego/dns/transip/) | [UKFast SafeDNS](https://go-acme.github.io/lego/dns/safedns/) | [Ultradns](https://go-acme.github.io/lego/dns/ultradns/) |
| [Variomedia](https://go-acme.github.io/lego/dns/variomedia/) | [VegaDNS](https://go-acme.github.io/lego/dns/vegadns/) | [Vercel](https://go-acme.github.io/lego/dns/vercel/) | [Versio.[nl/eu/uk]](https://go-acme.github.io/lego/dns/versio/) |
| [VinylDNS](https://go-acme.github.io/lego/dns/vinyldns/) | [VK Cloud](https://go-acme.github.io/lego/dns/vkcloud/) | [Vscale](https://go-acme.github.io/lego/dns/vscale/) | [Vultr](https://go-acme.github.io/lego/dns/vultr/) |
| [Webnames](https://go-acme.github.io/lego/dns/webnames/) | [Websupport](https://go-acme.github.io/lego/dns/websupport/) | [WEDOS](https://go-acme.github.io/lego/dns/wedos/) | [Yandex 360](https://go-acme.github.io/lego/dns/yandex360/) |
| [Yandex Cloud](https://go-acme.github.io/lego/dns/yandexcloud/) | [Yandex PDD](https://go-acme.github.io/lego/dns/yandex/) | [Zone.ee](https://go-acme.github.io/lego/dns/zoneee/) | [Zonomi](https://go-acme.github.io/lego/dns/zonomi/) |
| [Scaleway](https://go-acme.github.io/lego/dns/scaleway/) | [Selectel v2](https://go-acme.github.io/lego/dns/selectelv2/) | [Selectel](https://go-acme.github.io/lego/dns/selectel/) | [SelfHost.(de/eu)](https://go-acme.github.io/lego/dns/selfhostde/) |
| [Servercow](https://go-acme.github.io/lego/dns/servercow/) | [Shellrent](https://go-acme.github.io/lego/dns/shellrent/) | [Simply.com](https://go-acme.github.io/lego/dns/simply/) | [Sonic](https://go-acme.github.io/lego/dns/sonic/) |
| [Stackpath](https://go-acme.github.io/lego/dns/stackpath/) | [Tencent Cloud DNS](https://go-acme.github.io/lego/dns/tencentcloud/) | [TransIP](https://go-acme.github.io/lego/dns/transip/) | [UKFast SafeDNS](https://go-acme.github.io/lego/dns/safedns/) |
| [Ultradns](https://go-acme.github.io/lego/dns/ultradns/) | [Variomedia](https://go-acme.github.io/lego/dns/variomedia/) | [VegaDNS](https://go-acme.github.io/lego/dns/vegadns/) | [Vercel](https://go-acme.github.io/lego/dns/vercel/) |
| [Versio.[nl/eu/uk]](https://go-acme.github.io/lego/dns/versio/) | [VinylDNS](https://go-acme.github.io/lego/dns/vinyldns/) | [VK Cloud](https://go-acme.github.io/lego/dns/vkcloud/) | [Vscale](https://go-acme.github.io/lego/dns/vscale/) |
| [Vultr](https://go-acme.github.io/lego/dns/vultr/) | [Webnames](https://go-acme.github.io/lego/dns/webnames/) | [Websupport](https://go-acme.github.io/lego/dns/websupport/) | [WEDOS](https://go-acme.github.io/lego/dns/wedos/) |
| [Yandex 360](https://go-acme.github.io/lego/dns/yandex360/) | [Yandex Cloud](https://go-acme.github.io/lego/dns/yandexcloud/) | [Yandex PDD](https://go-acme.github.io/lego/dns/yandex/) | [Zone.ee](https://go-acme.github.io/lego/dns/zoneee/) |
| [Zonomi](https://go-acme.github.io/lego/dns/zonomi/) | | | |

<!-- END DNS PROVIDERS LIST -->

Expand Down
23 changes: 23 additions & 0 deletions cmd/zz_gen_cmd_dnshelp.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ func allDNSCodes() string {
"scaleway",
"selectel",
"selectelv2",
"selfhostde",
"servercow",
"shellrent",
"simply",
Expand Down Expand Up @@ -2553,6 +2554,28 @@ func displayDNSHelp(w io.Writer, name string) error {
ew.writeln()
ew.writeln(`More information: https://go-acme.github.io/lego/dns/selectelv2`)

case "selfhostde":
// generated from: providers/dns/selfhostde/selfhostde.toml
ew.writeln(`Configuration for SelfHost.(de|eu).`)
ew.writeln(`Code: 'selfhostde'`)
ew.writeln(`Since: 'v4.19.0'`)
ew.writeln()

ew.writeln(`Credentials:`)
ew.writeln(` - "SELFHOSTDE_PASSWORD": Password`)
ew.writeln(` - "SELFHOSTDE_RECORDS_MAPPING": Record IDs mapping with domains (ex: example.com:123:456,example.org:789,foo.example.com:147)`)
ew.writeln(` - "SELFHOSTDE_USERNAME": Username`)
ew.writeln()

ew.writeln(`Additional Configuration:`)
ew.writeln(` - "SELFHOSTDE_HTTP_TIMEOUT": API request timeout`)
ew.writeln(` - "SELFHOSTDE_POLLING_INTERVAL": Time between DNS propagation check`)
ew.writeln(` - "SELFHOSTDE_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
ew.writeln(` - "SELFHOSTDE_TTL": The TTL of the TXT record used for the DNS challenge`)

ew.writeln()
ew.writeln(`More information: https://go-acme.github.io/lego/dns/selfhostde`)

case "servercow":
// generated from: providers/dns/servercow/servercow.toml
ew.writeln(`Configuration for Servercow.`)
Expand Down
96 changes: 96 additions & 0 deletions docs/content/dns/zz_gen_selfhostde.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
---
title: "SelfHost.(de|eu)"
date: 2019-03-03T16:39:46+01:00
draft: false
slug: selfhostde
dnsprovider:
since: "v4.19.0"
code: "selfhostde"
url: "https://www.selfhost.de"
---

<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
<!-- providers/dns/selfhostde/selfhostde.toml -->
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->


Configuration for [SelfHost.(de|eu)](https://www.selfhost.de).


<!--more-->

- Code: `selfhostde`
- Since: v4.19.0


Here is an example bash command using the SelfHost.(de|eu) provider:

```bash
SELFHOSTDE_USERNAME=xxx \
SELFHOSTDE_PASSWORD=yyy \
SELFHOSTDE_RECORDS_MAPPING=my.example.com:123 \
lego --email you@example.com --dns selfhostde --domains my.example.org run
```




## Credentials

| Environment Variable Name | Description |
|-----------------------|-------------|
| `SELFHOSTDE_PASSWORD` | Password |
| `SELFHOSTDE_RECORDS_MAPPING` | Record IDs mapping with domains (ex: example.com:123:456,example.org:789,foo.example.com:147) |
| `SELFHOSTDE_USERNAME` | Username |

The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
More information [here]({{% ref "dns#configuration-and-credentials" %}}).


## Additional Configuration

| Environment Variable Name | Description |
|--------------------------------|-------------|
| `SELFHOSTDE_HTTP_TIMEOUT` | API request timeout |
| `SELFHOSTDE_POLLING_INTERVAL` | Time between DNS propagation check |
| `SELFHOSTDE_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation |
| `SELFHOSTDE_TTL` | The TTL of the TXT record used for the DNS challenge |

The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
More information [here]({{% ref "dns#configuration-and-credentials" %}}).

SelfHost.de doesn't have an API to create or delete TXT records,
there is only an "unofficial" and undocumented endpoint to update an existing TXT record.

So, before using lego to request a certificate for a given domain or wildcard (such as `my.example.org` or `*.my.example.org`),
you must create:

- one TXT record named `_acme-challenge.my.example.org` if you are **not** using wildcard for this domain.
- two TXT records named `_acme-challenge.my.example.org` if you are using wildcard for this domain.

After that you must edit the TXT record(s) to get the ID(s).

You then must prepare the `SELFHOSTDE_RECORDS_MAPPING` environment variable with the following format:

```
<domain_A>:<record_id_A1>:<record_id_A2>,<domain_B>:<record_id_B1>:<record_id_B2>,<domain_C>:<record_id_C1>:<record_id_C2>
```

where each group of domain + record ID(s) is separated with a comma (`,`),
and the domain and record ID(s) are separated with a colon (`:`).

For example, if you want to create or renew a certificate for `my.example.org`, `*.my.example.org`, and `other.example.org`,
you would need:

- two separate records for `_acme-challenge.my.example.org`
- and another separate record for `_acme-challenge.other.example.org`

The resulting environment variable would then be: `SELFHOSTDE_RECORDS_MAPPING=my.example.com:123:456,other.example.com:789`





<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
<!-- providers/dns/selfhostde/selfhostde.toml -->
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
2 changes: 1 addition & 1 deletion docs/data/zz_cli_help.toml
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ To display the documentation for a specific DNS provider, run:
$ lego dnshelp -c code
Supported DNS providers:
acme-dns, alidns, allinkl, arvancloud, auroradns, autodns, azure, azuredns, bindman, bluecat, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, constellix, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dynu, easydns, edgedns, efficientip, epik, exec, exoscale, freemyip, gandi, gandiv5, gcloud, gcore, glesys, godaddy, googledomains, hetzner, hostingde, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ipv64, iwantmyname, joker, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manual, metaname, mijnhost, mittwald, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nifcloud, njalla, nodion, ns1, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rcodezero, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, servercow, shellrent, simply, sonic, stackpath, tencentcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, vscale, vultr, webnames, websupport, wedos, yandex, yandex360, yandexcloud, zoneee, zonomi
acme-dns, alidns, allinkl, arvancloud, auroradns, autodns, azure, azuredns, bindman, bluecat, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, constellix, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dynu, easydns, edgedns, efficientip, epik, exec, exoscale, freemyip, gandi, gandiv5, gcloud, gcore, glesys, godaddy, googledomains, hetzner, hostingde, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ipv64, iwantmyname, joker, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manual, metaname, mijnhost, mittwald, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nifcloud, njalla, nodion, ns1, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rcodezero, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, stackpath, tencentcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, vscale, vultr, webnames, websupport, wedos, yandex, yandex360, yandexcloud, zoneee, zonomi
More information: https://go-acme.github.io/lego/dns
"""
3 changes: 3 additions & 0 deletions providers/dns/dns_providers.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ import (
"github.com/go-acme/lego/v4/providers/dns/scaleway"
"github.com/go-acme/lego/v4/providers/dns/selectel"
"github.com/go-acme/lego/v4/providers/dns/selectelv2"
"github.com/go-acme/lego/v4/providers/dns/selfhostde"
"github.com/go-acme/lego/v4/providers/dns/servercow"
"github.com/go-acme/lego/v4/providers/dns/shellrent"
"github.com/go-acme/lego/v4/providers/dns/simply"
Expand Down Expand Up @@ -369,6 +370,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) {
return selectel.NewDNSProvider()
case "selectelv2":
return selectelv2.NewDNSProvider()
case "selfhostde":
return selfhostde.NewDNSProvider()
case "servercow":
return servercow.NewDNSProvider()
case "shellrent":
Expand Down
66 changes: 66 additions & 0 deletions providers/dns/selfhostde/internal/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package internal

import (
"context"
"fmt"
"net/http"
"net/url"
"time"

"github.com/go-acme/lego/v4/providers/dns/internal/errutils"
)

const defaultBaseURL = "https://selfhost.de/cgi-bin/api.pl"

// Client the SelfHost client.
type Client struct {
username string
password string

baseURL string
HTTPClient *http.Client
}

// NewClient Creates a new Client.
func NewClient(username, password string) *Client {
return &Client{
username: username,
password: password,
baseURL: defaultBaseURL,
HTTPClient: &http.Client{Timeout: 5 * time.Second},
}
}

// UpdateTXTRecord updates content of an existing TXT record.
func (c *Client) UpdateTXTRecord(ctx context.Context, recordID, content string) error {
endpoint, err := url.Parse(c.baseURL)
if err != nil {
return fmt.Errorf("parse URL: %w", err)
}

query := endpoint.Query()
query.Set("username", c.username)
query.Set("password", c.password)
query.Set("rid", recordID)
query.Set("content", content)

endpoint.RawQuery = query.Encode()

req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint.String(), nil)
if err != nil {
return fmt.Errorf("new HTTP request: %w", err)
}

resp, err := c.HTTPClient.Do(req)
if err != nil {
return errutils.NewHTTPDoError(req, err)
}

defer func() { _ = resp.Body.Close() }()

if resp.StatusCode/100 != 2 {
return errutils.NewUnexpectedResponseStatusCodeError(req, resp)
}

return nil
}
65 changes: 65 additions & 0 deletions providers/dns/selfhostde/internal/client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package internal

import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"testing"

"github.com/stretchr/testify/require"
)

func setupTest(t *testing.T) (*Client, *http.ServeMux) {
t.Helper()

mux := http.NewServeMux()
server := httptest.NewServer(mux)
t.Cleanup(server.Close)

client := NewClient("user", "secret")
serverURL, err := url.Parse(server.URL)
require.NoError(t, err)

client.baseURL = serverURL.String()

return client, mux
}

func TestClient_UpdateTXTRecord(t *testing.T) {
client, mux := setupTest(t)

mux.HandleFunc("GET /", func(rw http.ResponseWriter, req *http.Request) {
query := req.URL.Query()

fields := map[string]string{
"username": "user",
"password": "secret",
"rid": "123456",
"content": "txt",
}

for k, v := range fields {
value := query.Get(k)
if value != v {
http.Error(rw, fmt.Sprintf("%s: unexpected value: %s (%s)", k, value, v), http.StatusBadRequest)
return
}
}
})

err := client.UpdateTXTRecord(context.Background(), "123456", "txt")
require.NoError(t, err)
}

func TestClient_UpdateTXTRecord_error(t *testing.T) {
client, mux := setupTest(t)

mux.HandleFunc("GET /", func(rw http.ResponseWriter, _ *http.Request) {
http.Error(rw, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
})

err := client.UpdateTXTRecord(context.Background(), "123456", "txt")
require.Error(t, err)
}
7 changes: 7 additions & 0 deletions providers/dns/selfhostde/internal/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# SelfHost.(de|eu)

SelfHost doesn't provide an official API documentation and there are no endpoints for create a TXT record or delete a TXT record.

## More

The documentation found at https://kirk.selfhost.de/cgi-bin/selfhost?p=document&name=api (PDF) describes the DynDNS/ddns API endpoint and is not used by our client.
Loading

0 comments on commit 20c8d6c

Please sign in to comment.