Skip to content

Commit 4efd1e1

Browse files
authored
Add DNS provider for Technitium (#2332)
1 parent f514292 commit 4efd1e1

File tree

14 files changed

+760
-8
lines changed

14 files changed

+760
-8
lines changed

README.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -200,37 +200,37 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns).
200200
<td><a href="https://go-acme.github.io/lego/dns/simply/">Simply.com</a></td>
201201
<td><a href="https://go-acme.github.io/lego/dns/sonic/">Sonic</a></td>
202202
<td><a href="https://go-acme.github.io/lego/dns/stackpath/">Stackpath</a></td>
203-
<td><a href="https://go-acme.github.io/lego/dns/tencentcloud/">Tencent Cloud DNS</a></td>
203+
<td><a href="https://go-acme.github.io/lego/dns/technitium/">Technitium</a></td>
204204
</tr><tr>
205+
<td><a href="https://go-acme.github.io/lego/dns/tencentcloud/">Tencent Cloud DNS</a></td>
205206
<td><a href="https://go-acme.github.io/lego/dns/timewebcloud/">Timeweb Cloud</a></td>
206207
<td><a href="https://go-acme.github.io/lego/dns/transip/">TransIP</a></td>
207208
<td><a href="https://go-acme.github.io/lego/dns/safedns/">UKFast SafeDNS</a></td>
208-
<td><a href="https://go-acme.github.io/lego/dns/ultradns/">Ultradns</a></td>
209209
</tr><tr>
210+
<td><a href="https://go-acme.github.io/lego/dns/ultradns/">Ultradns</a></td>
210211
<td><a href="https://go-acme.github.io/lego/dns/variomedia/">Variomedia</a></td>
211212
<td><a href="https://go-acme.github.io/lego/dns/vegadns/">VegaDNS</a></td>
212213
<td><a href="https://go-acme.github.io/lego/dns/vercel/">Vercel</a></td>
213-
<td><a href="https://go-acme.github.io/lego/dns/versio/">Versio.[nl|eu|uk]</a></td>
214214
</tr><tr>
215+
<td><a href="https://go-acme.github.io/lego/dns/versio/">Versio.[nl|eu|uk]</a></td>
215216
<td><a href="https://go-acme.github.io/lego/dns/vinyldns/">VinylDNS</a></td>
216217
<td><a href="https://go-acme.github.io/lego/dns/vkcloud/">VK Cloud</a></td>
217218
<td><a href="https://go-acme.github.io/lego/dns/volcengine/">Volcano Engine/火山引擎</a></td>
218-
<td><a href="https://go-acme.github.io/lego/dns/vscale/">Vscale</a></td>
219219
</tr><tr>
220+
<td><a href="https://go-acme.github.io/lego/dns/vscale/">Vscale</a></td>
220221
<td><a href="https://go-acme.github.io/lego/dns/vultr/">Vultr</a></td>
221222
<td><a href="https://go-acme.github.io/lego/dns/webnames/">Webnames</a></td>
222223
<td><a href="https://go-acme.github.io/lego/dns/websupport/">Websupport</a></td>
223-
<td><a href="https://go-acme.github.io/lego/dns/wedos/">WEDOS</a></td>
224224
</tr><tr>
225+
<td><a href="https://go-acme.github.io/lego/dns/wedos/">WEDOS</a></td>
225226
<td><a href="https://go-acme.github.io/lego/dns/yandex360/">Yandex 360</a></td>
226227
<td><a href="https://go-acme.github.io/lego/dns/yandexcloud/">Yandex Cloud</a></td>
227228
<td><a href="https://go-acme.github.io/lego/dns/yandex/">Yandex PDD</a></td>
228-
<td><a href="https://go-acme.github.io/lego/dns/zoneee/">Zone.ee</a></td>
229229
</tr><tr>
230+
<td><a href="https://go-acme.github.io/lego/dns/zoneee/">Zone.ee</a></td>
230231
<td><a href="https://go-acme.github.io/lego/dns/zonomi/">Zonomi</a></td>
231232
<td></td>
232233
<td></td>
233-
<td></td>
234234
</tr></table>
235235

236236
<!-- END DNS PROVIDERS LIST -->

cmd/zz_gen_cmd_dnshelp.go

Lines changed: 22 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/content/dns/zz_gen_technitium.md

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
---
2+
title: "Technitium"
3+
date: 2019-03-03T16:39:46+01:00
4+
draft: false
5+
slug: technitium
6+
dnsprovider:
7+
since: "v4.20.0"
8+
code: "technitium"
9+
url: "https://technitium.com/"
10+
---
11+
12+
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
13+
<!-- providers/dns/technitium/technitium.toml -->
14+
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
15+
16+
17+
Configuration for [Technitium](https://technitium.com/).
18+
19+
20+
<!--more-->
21+
22+
- Code: `technitium`
23+
- Since: v4.20.0
24+
25+
26+
Here is an example bash command using the Technitium provider:
27+
28+
```bash
29+
TECHNITIUM_SERVER_BASE_URL="https://localhost:5380" \
30+
TECHNITIUM_API_TOKEN="xxxxxxxxxxxxxxxxxxxxx" \
31+
lego --email you@example.com --dns technitium -d '*.example.com' -d example.com run
32+
```
33+
34+
35+
36+
37+
## Credentials
38+
39+
| Environment Variable Name | Description |
40+
|-----------------------|-------------|
41+
| `TECHNITIUM_API_TOKEN` | API token |
42+
| `TECHNITIUM_SERVER_BASE_URL` | Server base URL |
43+
44+
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
45+
More information [here]({{% ref "dns#configuration-and-credentials" %}}).
46+
47+
48+
## Additional Configuration
49+
50+
| Environment Variable Name | Description |
51+
|--------------------------------|-------------|
52+
| `TECHNITIUM_HTTP_TIMEOUT` | API request timeout |
53+
| `TECHNITIUM_POLLING_INTERVAL` | Time between DNS propagation check |
54+
| `TECHNITIUM_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation |
55+
| `TECHNITIUM_TTL` | The TTL of the TXT record used for the DNS challenge |
56+
57+
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
58+
More information [here]({{% ref "dns#configuration-and-credentials" %}}).
59+
60+
Technitium DNS Server supports Dynamic Updates (RFC2136) for primary zones,
61+
so you can also use the [RFC2136 provider](https://go-acme.github.io/lego/dns/rfc2136/index.html).
62+
63+
[RFC2136 provider](https://go-acme.github.io/lego/dns/rfc2136/index.html) is much better compared to the HTTP API option from security perspective.
64+
Technitium recommends to use it in production over the HTTP API.
65+
66+
67+
68+
## More information
69+
70+
- [API documentation](https://github.com/TechnitiumSoftware/DnsServer/blob/0f83d23e605956b66ac76921199e241d9cc061bd/APIDOCS.md)
71+
72+
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
73+
<!-- providers/dns/technitium/technitium.toml -->
74+
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->

docs/data/zz_cli_help.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ To display the documentation for a specific DNS provider, run:
141141
$ lego dnshelp -c code
142142
143143
Supported DNS providers:
144-
acme-dns, alidns, allinkl, arvancloud, auroradns, autodns, azure, azuredns, bindman, bluecat, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, constellix, corenetworks, 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, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, stackpath, tencentcloud, timewebcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, websupport, wedos, yandex, yandex360, yandexcloud, zoneee, zonomi
144+
acme-dns, alidns, allinkl, arvancloud, auroradns, autodns, azure, azuredns, bindman, bluecat, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, constellix, corenetworks, 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, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, stackpath, technitium, tencentcloud, timewebcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, websupport, wedos, yandex, yandex360, yandexcloud, zoneee, zonomi
145145
146146
More information: https://go-acme.github.io/lego/dns
147147
"""
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
package internal
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"errors"
7+
"fmt"
8+
"io"
9+
"net/http"
10+
"net/url"
11+
"strings"
12+
"time"
13+
14+
"github.com/go-acme/lego/v4/providers/dns/internal/errutils"
15+
querystring "github.com/google/go-querystring/query"
16+
)
17+
18+
const statusSuccess = "ok"
19+
20+
// Client the Technitium API client.
21+
type Client struct {
22+
apiToken string
23+
24+
baseURL *url.URL
25+
HTTPClient *http.Client
26+
}
27+
28+
// NewClient creates a new Client.
29+
func NewClient(baseURL, apiToken string) (*Client, error) {
30+
if apiToken == "" {
31+
return nil, errors.New("missing credentials")
32+
}
33+
34+
if baseURL == "" {
35+
return nil, errors.New("missing server URL")
36+
}
37+
38+
apiEndpoint, err := url.Parse(baseURL)
39+
if err != nil {
40+
return nil, err
41+
}
42+
43+
return &Client{
44+
apiToken: apiToken,
45+
baseURL: apiEndpoint,
46+
HTTPClient: &http.Client{Timeout: 10 * time.Second},
47+
}, nil
48+
}
49+
50+
// AddRecord adds a resource record for an authoritative zone.
51+
// https://github.com/TechnitiumSoftware/DnsServer/blob/master/APIDOCS.md#add-record
52+
func (c *Client) AddRecord(ctx context.Context, record Record) (*Record, error) {
53+
endpoint := c.baseURL.JoinPath("api", "zones", "records", "add")
54+
55+
req, err := c.newFormRequest(ctx, endpoint, record)
56+
if err != nil {
57+
return nil, fmt.Errorf("create request: %w", err)
58+
}
59+
60+
result := &APIResponse[AddRecordResponse]{}
61+
62+
err = c.do(req, result)
63+
if err != nil {
64+
return nil, err
65+
}
66+
67+
if result.Status != statusSuccess {
68+
return nil, result
69+
}
70+
71+
return result.Response.AddedRecord, nil
72+
}
73+
74+
// DeleteRecord deletes a record from an authoritative zone.
75+
// https://github.com/TechnitiumSoftware/DnsServer/blob/master/APIDOCS.md#delete-record
76+
func (c *Client) DeleteRecord(ctx context.Context, record Record) error {
77+
endpoint := c.baseURL.JoinPath("api", "zones", "records", "delete")
78+
79+
req, err := c.newFormRequest(ctx, endpoint, record)
80+
if err != nil {
81+
return fmt.Errorf("create request: %w", err)
82+
}
83+
84+
result := &APIResponse[any]{}
85+
86+
err = c.do(req, result)
87+
if err != nil {
88+
return err
89+
}
90+
91+
if result.Status != statusSuccess {
92+
return result
93+
}
94+
95+
return nil
96+
}
97+
98+
func (c *Client) do(req *http.Request, result any) error {
99+
resp, err := c.HTTPClient.Do(req)
100+
if err != nil {
101+
return errutils.NewHTTPDoError(req, err)
102+
}
103+
104+
defer func() { _ = resp.Body.Close() }()
105+
106+
if resp.StatusCode > http.StatusBadRequest {
107+
return parseError(req, resp)
108+
}
109+
110+
raw, err := io.ReadAll(resp.Body)
111+
if err != nil {
112+
return errutils.NewReadResponseError(req, resp.StatusCode, err)
113+
}
114+
115+
err = json.Unmarshal(raw, result)
116+
if err != nil {
117+
return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err)
118+
}
119+
120+
return nil
121+
}
122+
123+
func (c *Client) newFormRequest(ctx context.Context, endpoint *url.URL, payload any) (*http.Request, error) {
124+
values := url.Values{}
125+
126+
if payload != nil {
127+
var err error
128+
values, err = querystring.Values(payload)
129+
if err != nil {
130+
return nil, fmt.Errorf("failed to create request body: %w", err)
131+
}
132+
}
133+
134+
values.Set("token", c.apiToken)
135+
136+
req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint.String(), strings.NewReader(values.Encode()))
137+
if err != nil {
138+
return nil, fmt.Errorf("unable to create request: %w", err)
139+
}
140+
141+
if payload != nil {
142+
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
143+
}
144+
145+
return req, nil
146+
}
147+
148+
func parseError(req *http.Request, resp *http.Response) error {
149+
raw, _ := io.ReadAll(resp.Body)
150+
151+
var errAPI APIResponse[any]
152+
err := json.Unmarshal(raw, &errAPI)
153+
if err != nil {
154+
return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw)
155+
}
156+
157+
return &errAPI
158+
}

0 commit comments

Comments
 (0)