Skip to content

Commit

Permalink
Add DNS provider for Technitium (#2332)
Browse files Browse the repository at this point in the history
  • Loading branch information
ldez authored Nov 10, 2024
1 parent f514292 commit 4efd1e1
Show file tree
Hide file tree
Showing 14 changed files with 760 additions and 8 deletions.
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,37 +200,37 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns).
<td><a href="https://go-acme.github.io/lego/dns/simply/">Simply.com</a></td>
<td><a href="https://go-acme.github.io/lego/dns/sonic/">Sonic</a></td>
<td><a href="https://go-acme.github.io/lego/dns/stackpath/">Stackpath</a></td>
<td><a href="https://go-acme.github.io/lego/dns/tencentcloud/">Tencent Cloud DNS</a></td>
<td><a href="https://go-acme.github.io/lego/dns/technitium/">Technitium</a></td>
</tr><tr>
<td><a href="https://go-acme.github.io/lego/dns/tencentcloud/">Tencent Cloud DNS</a></td>
<td><a href="https://go-acme.github.io/lego/dns/timewebcloud/">Timeweb Cloud</a></td>
<td><a href="https://go-acme.github.io/lego/dns/transip/">TransIP</a></td>
<td><a href="https://go-acme.github.io/lego/dns/safedns/">UKFast SafeDNS</a></td>
<td><a href="https://go-acme.github.io/lego/dns/ultradns/">Ultradns</a></td>
</tr><tr>
<td><a href="https://go-acme.github.io/lego/dns/ultradns/">Ultradns</a></td>
<td><a href="https://go-acme.github.io/lego/dns/variomedia/">Variomedia</a></td>
<td><a href="https://go-acme.github.io/lego/dns/vegadns/">VegaDNS</a></td>
<td><a href="https://go-acme.github.io/lego/dns/vercel/">Vercel</a></td>
<td><a href="https://go-acme.github.io/lego/dns/versio/">Versio.[nl|eu|uk]</a></td>
</tr><tr>
<td><a href="https://go-acme.github.io/lego/dns/versio/">Versio.[nl|eu|uk]</a></td>
<td><a href="https://go-acme.github.io/lego/dns/vinyldns/">VinylDNS</a></td>
<td><a href="https://go-acme.github.io/lego/dns/vkcloud/">VK Cloud</a></td>
<td><a href="https://go-acme.github.io/lego/dns/volcengine/">Volcano Engine/火山引擎</a></td>
<td><a href="https://go-acme.github.io/lego/dns/vscale/">Vscale</a></td>
</tr><tr>
<td><a href="https://go-acme.github.io/lego/dns/vscale/">Vscale</a></td>
<td><a href="https://go-acme.github.io/lego/dns/vultr/">Vultr</a></td>
<td><a href="https://go-acme.github.io/lego/dns/webnames/">Webnames</a></td>
<td><a href="https://go-acme.github.io/lego/dns/websupport/">Websupport</a></td>
<td><a href="https://go-acme.github.io/lego/dns/wedos/">WEDOS</a></td>
</tr><tr>
<td><a href="https://go-acme.github.io/lego/dns/wedos/">WEDOS</a></td>
<td><a href="https://go-acme.github.io/lego/dns/yandex360/">Yandex 360</a></td>
<td><a href="https://go-acme.github.io/lego/dns/yandexcloud/">Yandex Cloud</a></td>
<td><a href="https://go-acme.github.io/lego/dns/yandex/">Yandex PDD</a></td>
<td><a href="https://go-acme.github.io/lego/dns/zoneee/">Zone.ee</a></td>
</tr><tr>
<td><a href="https://go-acme.github.io/lego/dns/zoneee/">Zone.ee</a></td>
<td><a href="https://go-acme.github.io/lego/dns/zonomi/">Zonomi</a></td>
<td></td>
<td></td>
<td></td>
</tr></table>

<!-- END DNS PROVIDERS LIST -->
Expand Down
22 changes: 22 additions & 0 deletions cmd/zz_gen_cmd_dnshelp.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

74 changes: 74 additions & 0 deletions docs/content/dns/zz_gen_technitium.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
---
title: "Technitium"
date: 2019-03-03T16:39:46+01:00
draft: false
slug: technitium
dnsprovider:
since: "v4.20.0"
code: "technitium"
url: "https://technitium.com/"
---

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


Configuration for [Technitium](https://technitium.com/).


<!--more-->

- Code: `technitium`
- Since: v4.20.0


Here is an example bash command using the Technitium provider:

```bash
TECHNITIUM_SERVER_BASE_URL="https://localhost:5380" \
TECHNITIUM_API_TOKEN="xxxxxxxxxxxxxxxxxxxxx" \
lego --email [email protected] --dns technitium -d '*.example.com' -d example.com run
```




## Credentials

| Environment Variable Name | Description |
|-----------------------|-------------|
| `TECHNITIUM_API_TOKEN` | API token |
| `TECHNITIUM_SERVER_BASE_URL` | Server base URL |

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 |
|--------------------------------|-------------|
| `TECHNITIUM_HTTP_TIMEOUT` | API request timeout |
| `TECHNITIUM_POLLING_INTERVAL` | Time between DNS propagation check |
| `TECHNITIUM_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation |
| `TECHNITIUM_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" %}}).

Technitium DNS Server supports Dynamic Updates (RFC2136) for primary zones,
so you can also use the [RFC2136 provider](https://go-acme.github.io/lego/dns/rfc2136/index.html).

[RFC2136 provider](https://go-acme.github.io/lego/dns/rfc2136/index.html) is much better compared to the HTTP API option from security perspective.
Technitium recommends to use it in production over the HTTP API.



## More information

- [API documentation](https://github.com/TechnitiumSoftware/DnsServer/blob/0f83d23e605956b66ac76921199e241d9cc061bd/APIDOCS.md)

<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
<!-- providers/dns/technitium/technitium.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 @@ -141,7 +141,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, 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
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
More information: https://go-acme.github.io/lego/dns
"""
158 changes: 158 additions & 0 deletions providers/dns/technitium/internal/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
package internal

import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"time"

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

const statusSuccess = "ok"

// Client the Technitium API client.
type Client struct {
apiToken string

baseURL *url.URL
HTTPClient *http.Client
}

// NewClient creates a new Client.
func NewClient(baseURL, apiToken string) (*Client, error) {
if apiToken == "" {
return nil, errors.New("missing credentials")
}

if baseURL == "" {
return nil, errors.New("missing server URL")
}

apiEndpoint, err := url.Parse(baseURL)
if err != nil {
return nil, err
}

return &Client{
apiToken: apiToken,
baseURL: apiEndpoint,
HTTPClient: &http.Client{Timeout: 10 * time.Second},
}, nil
}

// AddRecord adds a resource record for an authoritative zone.
// https://github.com/TechnitiumSoftware/DnsServer/blob/master/APIDOCS.md#add-record
func (c *Client) AddRecord(ctx context.Context, record Record) (*Record, error) {
endpoint := c.baseURL.JoinPath("api", "zones", "records", "add")

req, err := c.newFormRequest(ctx, endpoint, record)
if err != nil {
return nil, fmt.Errorf("create request: %w", err)
}

result := &APIResponse[AddRecordResponse]{}

err = c.do(req, result)
if err != nil {
return nil, err
}

if result.Status != statusSuccess {
return nil, result
}

return result.Response.AddedRecord, nil
}

// DeleteRecord deletes a record from an authoritative zone.
// https://github.com/TechnitiumSoftware/DnsServer/blob/master/APIDOCS.md#delete-record
func (c *Client) DeleteRecord(ctx context.Context, record Record) error {
endpoint := c.baseURL.JoinPath("api", "zones", "records", "delete")

req, err := c.newFormRequest(ctx, endpoint, record)
if err != nil {
return fmt.Errorf("create request: %w", err)
}

result := &APIResponse[any]{}

err = c.do(req, result)
if err != nil {
return err
}

if result.Status != statusSuccess {
return result
}

return nil
}

func (c *Client) do(req *http.Request, result any) error {
resp, err := c.HTTPClient.Do(req)
if err != nil {
return errutils.NewHTTPDoError(req, err)
}

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

if resp.StatusCode > http.StatusBadRequest {
return parseError(req, resp)
}

raw, err := io.ReadAll(resp.Body)
if err != nil {
return errutils.NewReadResponseError(req, resp.StatusCode, err)
}

err = json.Unmarshal(raw, result)
if err != nil {
return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err)
}

return nil
}

func (c *Client) newFormRequest(ctx context.Context, endpoint *url.URL, payload any) (*http.Request, error) {
values := url.Values{}

if payload != nil {
var err error
values, err = querystring.Values(payload)
if err != nil {
return nil, fmt.Errorf("failed to create request body: %w", err)
}
}

values.Set("token", c.apiToken)

req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint.String(), strings.NewReader(values.Encode()))
if err != nil {
return nil, fmt.Errorf("unable to create request: %w", err)
}

if payload != nil {
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
}

return req, nil
}

func parseError(req *http.Request, resp *http.Response) error {
raw, _ := io.ReadAll(resp.Body)

var errAPI APIResponse[any]
err := json.Unmarshal(raw, &errAPI)
if err != nil {
return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw)
}

return &errAPI
}
Loading

0 comments on commit 4efd1e1

Please sign in to comment.