Skip to content

Commit 3ba40ff

Browse files
L-Nafaryusldez
andauthored
Add DNS provider for Webnames (go-acme#2077)
Co-authored-by: Fernandez Ludovic <[email protected]>
1 parent 68dc83a commit 3ba40ff

File tree

13 files changed

+649
-4
lines changed

13 files changed

+649
-4
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,9 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns).
8282
| [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/) |
8383
| [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/) |
8484
| [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/) |
85-
| [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/) | [Websupport](https://go-acme.github.io/lego/dns/websupport/) |
86-
| [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/) |
87-
| [Zone.ee](https://go-acme.github.io/lego/dns/zoneee/) | [Zonomi](https://go-acme.github.io/lego/dns/zonomi/) | | |
85+
| [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/) |
86+
| [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/) |
87+
| [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/) | |
8888

8989
<!-- END DNS PROVIDERS LIST -->
9090

cmd/zz_gen_cmd_dnshelp.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ func allDNSCodes() string {
132132
"vkcloud",
133133
"vscale",
134134
"vultr",
135+
"webnames",
135136
"websupport",
136137
"wedos",
137138
"yandex",
@@ -2667,6 +2668,26 @@ func displayDNSHelp(w io.Writer, name string) error {
26672668
ew.writeln()
26682669
ew.writeln(`More information: https://go-acme.github.io/lego/dns/vultr`)
26692670

2671+
case "webnames":
2672+
// generated from: providers/dns/webnames/webnames.toml
2673+
ew.writeln(`Configuration for Webnames.`)
2674+
ew.writeln(`Code: 'webnames'`)
2675+
ew.writeln(`Since: 'v4.15.0'`)
2676+
ew.writeln()
2677+
2678+
ew.writeln(`Credentials:`)
2679+
ew.writeln(` - "WEBNAMES_API_KEY": Domain API key`)
2680+
ew.writeln()
2681+
2682+
ew.writeln(`Additional Configuration:`)
2683+
ew.writeln(` - "WEBNAMES_HTTP_TIMEOUT": API request timeout`)
2684+
ew.writeln(` - "WEBNAMES_POLLING_INTERVAL": Time between DNS propagation check`)
2685+
ew.writeln(` - "WEBNAMES_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
2686+
ew.writeln(` - "WEBNAMES_TTL": The TTL of the TXT record used for the DNS challenge`)
2687+
2688+
ew.writeln()
2689+
ew.writeln(`More information: https://go-acme.github.io/lego/dns/webnames`)
2690+
26702691
case "websupport":
26712692
// generated from: providers/dns/websupport/websupport.toml
26722693
ew.writeln(`Configuration for Websupport.`)
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
---
2+
title: "Webnames"
3+
date: 2019-03-03T16:39:46+01:00
4+
draft: false
5+
slug: webnames
6+
dnsprovider:
7+
since: "v4.15.0"
8+
code: "webnames"
9+
url: "https://www.webnames.ru/"
10+
---
11+
12+
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
13+
<!-- providers/dns/webnames/webnames.toml -->
14+
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
15+
16+
17+
Configuration for [Webnames](https://www.webnames.ru/).
18+
19+
20+
<!--more-->
21+
22+
- Code: `webnames`
23+
- Since: v4.15.0
24+
25+
26+
Here is an example bash command using the Webnames provider:
27+
28+
```bash
29+
WEBNAMES_API_KEY=xxxxxx \
30+
lego --email [email protected] --dns webnames --domains my.example.org run
31+
```
32+
33+
34+
35+
36+
## Credentials
37+
38+
| Environment Variable Name | Description |
39+
|-----------------------|-------------|
40+
| `WEBNAMES_API_KEY` | Domain API key |
41+
42+
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
43+
More information [here]({{< ref "dns#configuration-and-credentials" >}}).
44+
45+
46+
## Additional Configuration
47+
48+
| Environment Variable Name | Description |
49+
|--------------------------------|-------------|
50+
| `WEBNAMES_HTTP_TIMEOUT` | API request timeout |
51+
| `WEBNAMES_POLLING_INTERVAL` | Time between DNS propagation check |
52+
| `WEBNAMES_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation |
53+
| `WEBNAMES_TTL` | The TTL of the TXT record used for the DNS challenge |
54+
55+
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
56+
More information [here]({{< ref "dns#configuration-and-credentials" >}}).
57+
58+
## API Key
59+
60+
To obtain the key, you need to change the DNS server to `*.nameself.com`: Personal account / My domains and services / Select the required domain / DNS servers
61+
62+
The API key can be found: Personal account / My domains and services / Select the required domain / Zone management / acme.sh or certbot settings
63+
64+
65+
66+
## More information
67+
68+
- [API documentation](https://github.com/regtime-ltd/certbot-dns-webnames)
69+
70+
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
71+
<!-- providers/dns/webnames/webnames.toml -->
72+
<!-- 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
@@ -137,7 +137,7 @@ To display the documentation for a specific DNS provider, run:
137137
$ lego dnshelp -c code
138138
139139
Supported DNS providers:
140-
acme-dns, alidns, allinkl, arvancloud, auroradns, autodns, azure, azuredns, bindman, bluecat, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, constellix, derak, desec, designate, digitalocean, 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, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ipv64, iwantmyname, joker, liara, lightsail, linode, liquidweb, loopia, luadns, manual, metaname, 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, servercow, simply, sonic, stackpath, tencentcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, vscale, vultr, websupport, wedos, yandex, yandex360, yandexcloud, zoneee, zonomi
140+
acme-dns, alidns, allinkl, arvancloud, auroradns, autodns, azure, azuredns, bindman, bluecat, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, constellix, derak, desec, designate, digitalocean, 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, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ipv64, iwantmyname, joker, liara, lightsail, linode, liquidweb, loopia, luadns, manual, metaname, 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, servercow, simply, sonic, stackpath, tencentcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, vscale, vultr, webnames, websupport, wedos, yandex, yandex360, yandexcloud, zoneee, zonomi
141141
142142
More information: https://go-acme.github.io/lego/dns
143143
"""

providers/dns/dns_providers.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ import (
122122
"github.com/go-acme/lego/v4/providers/dns/vkcloud"
123123
"github.com/go-acme/lego/v4/providers/dns/vscale"
124124
"github.com/go-acme/lego/v4/providers/dns/vultr"
125+
"github.com/go-acme/lego/v4/providers/dns/webnames"
125126
"github.com/go-acme/lego/v4/providers/dns/websupport"
126127
"github.com/go-acme/lego/v4/providers/dns/wedos"
127128
"github.com/go-acme/lego/v4/providers/dns/yandex"
@@ -370,6 +371,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) {
370371
return vscale.NewDNSProvider()
371372
case "vultr":
372373
return vultr.NewDNSProvider()
374+
case "webnames":
375+
return webnames.NewDNSProvider()
373376
case "websupport":
374377
return websupport.NewDNSProvider()
375378
case "wedos":
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package internal
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"io"
8+
"net/http"
9+
"net/url"
10+
"strings"
11+
"time"
12+
13+
"github.com/go-acme/lego/v4/providers/dns/internal/errutils"
14+
)
15+
16+
const defaultBaseURL = "https://www.webnames.ru/scripts/json_domain_zone_manager.pl"
17+
18+
// Client the Webnames API client.
19+
type Client struct {
20+
apiKey string
21+
22+
baseURL string
23+
HTTPClient *http.Client
24+
}
25+
26+
// NewClient Creates a new Client.
27+
func NewClient(apiKey string) *Client {
28+
return &Client{
29+
apiKey: apiKey,
30+
baseURL: defaultBaseURL,
31+
HTTPClient: &http.Client{Timeout: 10 * time.Second},
32+
}
33+
}
34+
35+
// AddTXTRecord adds a TXT record.
36+
// Inspired by https://github.com/regtime-ltd/certbot-dns-webnames/blob/master/authenticator.sh
37+
func (c *Client) AddTXTRecord(ctx context.Context, domain, subDomain, value string) error {
38+
data := url.Values{}
39+
data.Set("domain", domain)
40+
data.Set("type", "TXT")
41+
data.Set("record", subDomain+":"+value)
42+
data.Set("action", "add")
43+
44+
return c.doRequest(ctx, data)
45+
}
46+
47+
// RemoveTXTRecord removes a TXT record.
48+
// Inspired by https://github.com/regtime-ltd/certbot-dns-webnames/blob/master/cleanup.sh
49+
func (c *Client) RemoveTXTRecord(ctx context.Context, domain, subDomain, value string) error {
50+
data := url.Values{}
51+
data.Set("domain", domain)
52+
data.Set("type", "TXT")
53+
data.Set("record", subDomain+":"+value)
54+
data.Set("action", "delete")
55+
56+
return c.doRequest(ctx, data)
57+
}
58+
59+
func (c *Client) doRequest(ctx context.Context, data url.Values) error {
60+
data.Set("apikey", c.apiKey)
61+
62+
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.baseURL, strings.NewReader(data.Encode()))
63+
if err != nil {
64+
return err
65+
}
66+
67+
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
68+
69+
resp, err := c.HTTPClient.Do(req)
70+
if err != nil {
71+
return errutils.NewHTTPDoError(req, err)
72+
}
73+
74+
defer func() { _ = resp.Body.Close() }()
75+
76+
if resp.StatusCode/100 != 2 {
77+
return errutils.NewUnexpectedResponseStatusCodeError(req, resp)
78+
}
79+
80+
raw, err := io.ReadAll(resp.Body)
81+
if err != nil {
82+
return errutils.NewReadResponseError(req, resp.StatusCode, err)
83+
}
84+
85+
var r APIResponse
86+
err = json.Unmarshal(raw, &r)
87+
if err != nil {
88+
return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err)
89+
}
90+
91+
if r.Result == "OK" {
92+
return nil
93+
}
94+
95+
return fmt.Errorf("%s: %s", r.Result, r.Details)
96+
}
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
package internal
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"io"
7+
"net/http"
8+
"net/http/httptest"
9+
"net/url"
10+
"os"
11+
"path"
12+
"testing"
13+
14+
"github.com/stretchr/testify/require"
15+
)
16+
17+
func setupTest(t *testing.T, filename string, expectedParams url.Values) *Client {
18+
t.Helper()
19+
20+
mux := http.NewServeMux()
21+
22+
mux.HandleFunc("/", func(rw http.ResponseWriter, req *http.Request) {
23+
if req.Method != http.MethodPost {
24+
http.Error(rw, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
25+
return
26+
}
27+
28+
if req.Header.Get("Content-Type") != "application/x-www-form-urlencoded" {
29+
http.Error(rw, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
30+
return
31+
}
32+
33+
err := req.ParseForm()
34+
if err != nil {
35+
http.Error(rw, err.Error(), http.StatusBadRequest)
36+
return
37+
}
38+
39+
for k, v := range expectedParams {
40+
val := req.PostForm.Get(k)
41+
if len(v) == 0 {
42+
http.Error(rw, fmt.Sprintf("%s: no value", k), http.StatusBadRequest)
43+
return
44+
}
45+
46+
if val != v[0] {
47+
http.Error(rw, fmt.Sprintf("%s: invalid value: %s != %s", k, val, v[0]), http.StatusBadRequest)
48+
return
49+
}
50+
}
51+
52+
file, err := os.Open(path.Join("fixtures", filename))
53+
if err != nil {
54+
http.Error(rw, err.Error(), http.StatusInternalServerError)
55+
return
56+
}
57+
defer func() { _ = file.Close() }()
58+
59+
_, err = io.Copy(rw, file)
60+
if err != nil {
61+
http.Error(rw, err.Error(), http.StatusInternalServerError)
62+
return
63+
}
64+
})
65+
66+
server := httptest.NewServer(mux)
67+
68+
client := NewClient("secret")
69+
client.baseURL = server.URL
70+
client.HTTPClient = server.Client()
71+
72+
return client
73+
}
74+
75+
func TestClient_AddTXTRecord(t *testing.T) {
76+
testCases := []struct {
77+
desc string
78+
filename string
79+
require require.ErrorAssertionFunc
80+
}{
81+
{
82+
desc: "ok",
83+
filename: "ok.json",
84+
require: require.NoError,
85+
},
86+
{
87+
desc: "error",
88+
filename: "error.json",
89+
require: require.Error,
90+
},
91+
}
92+
93+
for _, test := range testCases {
94+
test := test
95+
t.Run(test.desc, func(t *testing.T) {
96+
t.Parallel()
97+
98+
data := url.Values{}
99+
data.Set("domain", "example.com")
100+
data.Set("type", "TXT")
101+
data.Set("record", "foo:txtTXTtxt")
102+
data.Set("action", "add")
103+
104+
client := setupTest(t, test.filename, data)
105+
106+
domain := "example.com"
107+
subDomain := "foo"
108+
content := "txtTXTtxt"
109+
110+
err := client.AddTXTRecord(context.Background(), domain, subDomain, content)
111+
test.require(t, err)
112+
})
113+
}
114+
}
115+
116+
func TestClient_RemoveTxtRecord(t *testing.T) {
117+
testCases := []struct {
118+
desc string
119+
filename string
120+
require require.ErrorAssertionFunc
121+
}{
122+
{
123+
desc: "ok",
124+
filename: "ok.json",
125+
require: require.NoError,
126+
},
127+
{
128+
desc: "error",
129+
filename: "error.json",
130+
require: require.Error,
131+
},
132+
}
133+
134+
for _, test := range testCases {
135+
test := test
136+
t.Run(test.desc, func(t *testing.T) {
137+
t.Parallel()
138+
139+
data := url.Values{}
140+
data.Set("domain", "example.com")
141+
data.Set("type", "TXT")
142+
data.Set("record", "foo:txtTXTtxt")
143+
data.Set("action", "delete")
144+
145+
client := setupTest(t, test.filename, data)
146+
147+
domain := "example.com"
148+
subDomain := "foo"
149+
content := "txtTXTtxt"
150+
151+
err := client.RemoveTXTRecord(context.Background(), domain, subDomain, content)
152+
test.require(t, err)
153+
})
154+
}
155+
}

0 commit comments

Comments
 (0)