Skip to content

Commit 2347db6

Browse files
committed
Add DNS provider for RU CENTER (go-acme#1891)
1 parent 1a16d1a commit 2347db6

File tree

9 files changed

+1065
-8
lines changed

9 files changed

+1065
-8
lines changed

README.md

+8-7
Original file line numberDiff line numberDiff line change
@@ -71,13 +71,14 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns).
7171
| [Nicmanager](https://go-acme.github.io/lego/dns/nicmanager/) | [NIFCloud](https://go-acme.github.io/lego/dns/nifcloud/) | [Njalla](https://go-acme.github.io/lego/dns/njalla/) | [Nodion](https://go-acme.github.io/lego/dns/nodion/) |
7272
| [NS1](https://go-acme.github.io/lego/dns/ns1/) | [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/) |
7373
| [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/) |
74-
| [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/) |
75-
| [Scaleway](https://go-acme.github.io/lego/dns/scaleway/) | [Selectel](https://go-acme.github.io/lego/dns/selectel/) | [Servercow](https://go-acme.github.io/lego/dns/servercow/) | [Simply.com](https://go-acme.github.io/lego/dns/simply/) |
76-
| [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/) |
77-
| [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/) |
78-
| [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/) |
79-
| [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/) | [WEDOS](https://go-acme.github.io/lego/dns/wedos/) |
80-
| [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/) |
74+
| [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/) | [RU CENTER](https://go-acme.github.io/lego/dns/nicru/) |
75+
| [Sakura Cloud](https://go-acme.github.io/lego/dns/sakuracloud/) | [Scaleway](https://go-acme.github.io/lego/dns/scaleway/) | [Selectel](https://go-acme.github.io/lego/dns/selectel/) | [Servercow](https://go-acme.github.io/lego/dns/servercow/) |
76+
| [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/) |
77+
| [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/) |
78+
| [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/) |
79+
| [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/) |
80+
| [WEDOS](https://go-acme.github.io/lego/dns/wedos/) | [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/) |
81+
| [Zonomi](https://go-acme.github.io/lego/dns/zonomi/) | | | |
8182

8283
<!-- END DNS PROVIDERS LIST -->
8384

cmd/zz_gen_cmd_dnshelp.go

+25
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ func allDNSCodes() string {
8989
"netcup",
9090
"netlify",
9191
"nicmanager",
92+
"nicru",
9293
"nifcloud",
9394
"njalla",
9495
"nodion",
@@ -1718,6 +1719,30 @@ func displayDNSHelp(w io.Writer, name string) error {
17181719
ew.writeln()
17191720
ew.writeln(`More information: https://go-acme.github.io/lego/dns/nicmanager`)
17201721

1722+
case "nicru":
1723+
// generated from: providers/dns/nicru/nicru.toml
1724+
ew.writeln(`Configuration for RU CENTER.`)
1725+
ew.writeln(`Code: 'nicru'`)
1726+
ew.writeln(`Since: 'v4.11.0'`)
1727+
ew.writeln()
1728+
1729+
ew.writeln(`Credentials:`)
1730+
ew.writeln(` - "NIC_RU_PASSWORD": Password for account in RU CENTER`)
1731+
ew.writeln(` - "NIC_RU_SECRET": Secret for application in DNS-hosting RU CENTER`)
1732+
ew.writeln(` - "NIC_RU_SERVICE_ID": Service ID for application in DNS-hosting RU CENTER`)
1733+
ew.writeln(` - "NIC_RU_SERVICE_NAME": Service Name for DNS-hosting RU CENTER`)
1734+
ew.writeln(` - "NIC_RU_USER": Agreement for account in RU CENTER`)
1735+
ew.writeln()
1736+
1737+
ew.writeln(`Additional Configuration:`)
1738+
ew.writeln(` - "NIC_RU_HTTP_TIMEOUT": API request timeout`)
1739+
ew.writeln(` - "NIC_RU_POLLING_INTERVAL": Time between DNS propagation check`)
1740+
ew.writeln(` - "NIC_RU_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
1741+
ew.writeln(` - "NIC_RU_TTL": The TTL of the TXT record used for the DNS challenge`)
1742+
1743+
ew.writeln()
1744+
ew.writeln(`More information: https://go-acme.github.io/lego/dns/nicru`)
1745+
17211746
case "nifcloud":
17221747
// generated from: providers/dns/nifcloud/nifcloud.toml
17231748
ew.writeln(`Configuration for NIFCloud.`)

docs/data/zz_cli_help.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ To display the documentation for a specific DNS provider, run:
125125
$ lego dnshelp -c code
126126
127127
Supported DNS providers:
128-
acme-dns, alidns, allinkl, arvancloud, auroradns, autodns, azure, bindman, bluecat, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudxns, conoha, constellix, desec, designate, digitalocean, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dynu, easydns, edgedns, epik, exec, exoscale, freemyip, gandi, gandiv5, gcloud, gcore, glesys, godaddy, googledomains, hetzner, hostingde, hosttech, httpreq, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, iwantmyname, joker, liara, lightsail, linode, liquidweb, loopia, luadns, manual, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nifcloud, njalla, nodion, ns1, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, 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, yandexcloud, zoneee, zonomi
128+
acme-dns, alidns, allinkl, arvancloud, auroradns, autodns, azure, bindman, bluecat, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudxns, conoha, constellix, desec, designate, digitalocean, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dynu, easydns, edgedns, epik, exec, exoscale, freemyip, gandi, gandiv5, gcloud, gcore, glesys, godaddy, googledomains, hetzner, hostingde, hosttech, httpreq, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, iwantmyname, joker, liara, lightsail, linode, liquidweb, loopia, luadns, manual, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, 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, yandexcloud, zoneee, zonomi
129129
130130
More information: https://go-acme.github.io/lego/dns
131131
"""

providers/dns/dns_providers.go

+3
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ import (
8080
"github.com/go-acme/lego/v4/providers/dns/netcup"
8181
"github.com/go-acme/lego/v4/providers/dns/netlify"
8282
"github.com/go-acme/lego/v4/providers/dns/nicmanager"
83+
"github.com/go-acme/lego/v4/providers/dns/nicru"
8384
"github.com/go-acme/lego/v4/providers/dns/nifcloud"
8485
"github.com/go-acme/lego/v4/providers/dns/njalla"
8586
"github.com/go-acme/lego/v4/providers/dns/nodion"
@@ -277,6 +278,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) {
277278
return netlify.NewDNSProvider()
278279
case "nicmanager":
279280
return nicmanager.NewDNSProvider()
281+
case "nicru":
282+
return nicru.NewDNSProvider()
280283
case "nifcloud":
281284
return nifcloud.NewDNSProvider()
282285
case "njalla":
+297
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
1+
package internal
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"encoding/xml"
7+
"errors"
8+
"fmt"
9+
"golang.org/x/oauth2"
10+
"net/http"
11+
"strconv"
12+
)
13+
14+
const (
15+
BaseURL = `https://api.nic.ru`
16+
TokenURL = BaseURL + `/oauth/token`
17+
GetZonesUrlPattern = BaseURL + `/dns-master/services/%s/zones`
18+
GetRecordsUrlPattern = BaseURL + `/dns-master/services/%s/zones/%s/records`
19+
DeleteRecordsUrlPattern = BaseURL + `/dns-master/services/%s/zones/%s/records/%d`
20+
AddRecordsUrlPattern = BaseURL + `/dns-master/services/%s/zones/%s/records`
21+
CommitUrlPattern = BaseURL + `/dns-master/services/%s/zones/%s/commit`
22+
SuccessStatus = `success`
23+
OAuth2Scope = `.+:/dns-master/.+`
24+
)
25+
26+
// Provider facilitates DNS record manipulation with NIC.ru.
27+
type Provider struct {
28+
OAuth2ClientID string `json:"oauth2_client_id"`
29+
OAuth2SecretID string `json:"oauth2_secret_id"`
30+
Username string `json:"username"`
31+
Password string `json:"password"`
32+
ServiceName string `json:"service_name"`
33+
}
34+
35+
type Client struct {
36+
client *http.Client
37+
provider *Provider
38+
token string
39+
}
40+
41+
func NewClient(provider *Provider) (*Client, error) {
42+
client := Client{provider: provider}
43+
err := client.validateAuthOptions()
44+
if err != nil {
45+
return nil, err
46+
}
47+
return &client, nil
48+
}
49+
50+
func (client *Client) GetOauth2Client() error {
51+
ctx := context.TODO()
52+
53+
oauth2Config := oauth2.Config{
54+
ClientID: client.provider.OAuth2ClientID,
55+
ClientSecret: client.provider.OAuth2SecretID,
56+
Endpoint: oauth2.Endpoint{
57+
TokenURL: TokenURL,
58+
AuthStyle: oauth2.AuthStyleInParams,
59+
},
60+
Scopes: []string{OAuth2Scope},
61+
}
62+
63+
oauth2Token, err := oauth2Config.PasswordCredentialsToken(ctx, client.provider.Username, client.provider.Password)
64+
if err != nil {
65+
return fmt.Errorf("nicru: %s", err.Error())
66+
}
67+
68+
client.client = oauth2Config.Client(ctx, oauth2Token)
69+
return nil
70+
}
71+
72+
func (client *Client) Do(r *http.Request) (*http.Response, error) {
73+
if client.client == nil {
74+
err := client.GetOauth2Client()
75+
if err != nil {
76+
return nil, err
77+
}
78+
}
79+
return client.client.Do(r)
80+
}
81+
82+
func (client *Client) GetZones() ([]*Zone, error) {
83+
request, err := http.NewRequest(http.MethodGet, fmt.Sprintf(GetZonesUrlPattern, client.provider.ServiceName), nil)
84+
if err != nil {
85+
return nil, err
86+
}
87+
response, err := client.Do(request)
88+
if err != nil {
89+
return nil, err
90+
}
91+
92+
buf := bytes.NewBuffer(nil)
93+
if _, err := buf.ReadFrom(response.Body); err != nil {
94+
return nil, err
95+
}
96+
97+
apiResponse := &Response{}
98+
if err := xml.NewDecoder(buf).Decode(&apiResponse); err != nil {
99+
return nil, err
100+
} else {
101+
var zones []*Zone
102+
for _, zone := range apiResponse.Data.Zone {
103+
zones = append(zones, zone)
104+
}
105+
return zones, nil
106+
}
107+
}
108+
109+
func (client *Client) GetRecords(fqdn string) ([]*RR, error) {
110+
request, err := http.NewRequest(
111+
http.MethodGet,
112+
fmt.Sprintf(GetRecordsUrlPattern, client.provider.ServiceName, fqdn),
113+
nil)
114+
if err != nil {
115+
return nil, err
116+
}
117+
response, err := client.Do(request)
118+
if err != nil {
119+
return nil, err
120+
}
121+
122+
buf := bytes.NewBuffer(nil)
123+
if _, err := buf.ReadFrom(response.Body); err != nil {
124+
return nil, err
125+
}
126+
127+
apiResponse := &Response{}
128+
if err := xml.NewDecoder(buf).Decode(&apiResponse); err != nil {
129+
return nil, err
130+
} else {
131+
var records []*RR
132+
for _, zone := range apiResponse.Data.Zone {
133+
records = append(records, zone.Rr...)
134+
}
135+
return records, nil
136+
}
137+
}
138+
139+
func (client *Client) add(zoneName string, request *Request) (*Response, error) {
140+
141+
buf := bytes.NewBuffer(nil)
142+
if err := xml.NewEncoder(buf).Encode(request); err != nil {
143+
return nil, err
144+
}
145+
146+
url := fmt.Sprintf(AddRecordsUrlPattern, client.provider.ServiceName, zoneName)
147+
148+
req, err := http.NewRequest(http.MethodPut, url, buf)
149+
if err != nil {
150+
return nil, err
151+
}
152+
153+
response, err := client.Do(req)
154+
if err != nil {
155+
return nil, err
156+
}
157+
158+
buf = bytes.NewBuffer(nil)
159+
if _, err := buf.ReadFrom(response.Body); err != nil {
160+
return nil, err
161+
}
162+
163+
apiResponse := &Response{}
164+
if err := xml.NewDecoder(buf).Decode(&apiResponse); err != nil {
165+
return nil, err
166+
}
167+
168+
if apiResponse.Status != SuccessStatus {
169+
return nil, fmt.Errorf(describeError(apiResponse.Errors.Error))
170+
} else {
171+
return apiResponse, nil
172+
}
173+
}
174+
175+
func (client *Client) deleteRecord(zoneName string, id int) (*Response, error) {
176+
url := fmt.Sprintf(DeleteRecordsUrlPattern, client.provider.ServiceName, zoneName, id)
177+
req, err := http.NewRequest(http.MethodDelete, url, nil)
178+
if err != nil {
179+
return nil, err
180+
}
181+
response, err := client.Do(req)
182+
if err != nil {
183+
return nil, err
184+
}
185+
apiResponse := Response{}
186+
if err := xml.NewDecoder(response.Body).Decode(&apiResponse); err != nil {
187+
return nil, err
188+
}
189+
if apiResponse.Status != SuccessStatus {
190+
return nil, err
191+
} else {
192+
return &apiResponse, nil
193+
}
194+
}
195+
196+
func (client *Client) GetTXTRecords(fqdn string) ([]*Txt, error) {
197+
records, err := client.GetRecords(fqdn)
198+
if err != nil {
199+
return nil, err
200+
}
201+
202+
txt := make([]*Txt, 0)
203+
for _, record := range records {
204+
if record.Txt != nil {
205+
txt = append(txt, record.Txt)
206+
}
207+
}
208+
209+
return txt, nil
210+
}
211+
212+
func (client *Client) AddTxtRecord(zoneName string, name string, content string, ttl int) (*Response, error) {
213+
request := &Request{
214+
RrList: &RrList{
215+
Rr: []*RR{},
216+
},
217+
}
218+
request.RrList.Rr = append(request.RrList.Rr, &RR{
219+
Name: name,
220+
Ttl: strconv.Itoa(ttl),
221+
Type: `TXT`,
222+
Txt: &Txt{
223+
String: content,
224+
},
225+
})
226+
227+
return client.add(zoneName, request)
228+
}
229+
230+
func (client *Client) DeleteRecord(zoneName string, id int) (*Response, error) {
231+
url := fmt.Sprintf(DeleteRecordsUrlPattern, client.provider.ServiceName, zoneName, id)
232+
req, err := http.NewRequest(http.MethodDelete, url, nil)
233+
if err != nil {
234+
return nil, err
235+
}
236+
response, err := client.Do(req)
237+
if err != nil {
238+
return nil, err
239+
}
240+
apiResponse := Response{}
241+
if err := xml.NewDecoder(response.Body).Decode(&apiResponse); err != nil {
242+
return nil, err
243+
}
244+
if apiResponse.Status != SuccessStatus {
245+
return nil, err
246+
} else {
247+
return &apiResponse, nil
248+
}
249+
}
250+
251+
func (client *Client) CommitZone(zoneName string) (*Response, error) {
252+
url := fmt.Sprintf(CommitUrlPattern, client.provider.ServiceName, zoneName)
253+
request, err := http.NewRequest(http.MethodPost, url, nil)
254+
if err != nil {
255+
return nil, err
256+
}
257+
response, err := client.Do(request)
258+
if err != nil {
259+
return nil, err
260+
}
261+
apiResponse := Response{}
262+
if err := xml.NewDecoder(response.Body).Decode(&apiResponse); err != nil {
263+
return nil, err
264+
}
265+
if apiResponse.Status != SuccessStatus {
266+
return nil, err
267+
} else {
268+
return &apiResponse, nil
269+
}
270+
}
271+
272+
func (client *Client) validateAuthOptions() error {
273+
274+
msg := " is missing in credentials information"
275+
276+
if client.provider.ServiceName == "" {
277+
return errors.New("service name" + msg)
278+
}
279+
280+
if client.provider.Username == "" {
281+
return errors.New("username" + msg)
282+
}
283+
284+
if client.provider.Password == "" {
285+
return errors.New("password" + msg)
286+
}
287+
288+
if client.provider.OAuth2ClientID == "" {
289+
return errors.New("serviceId" + msg)
290+
}
291+
292+
if client.provider.OAuth2SecretID == "" {
293+
return errors.New("secret" + msg)
294+
}
295+
296+
return nil
297+
}

0 commit comments

Comments
 (0)