Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add DNS provider for SelfHost.(de|eu) #2278

Merged
merged 10 commits into from
Sep 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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