Skip to content

Commit ae78237

Browse files
authored
Add DNS provider for cloud.ru (#1968)
1 parent 6c13564 commit ae78237

File tree

19 files changed

+1190
-29
lines changed

19 files changed

+1190
-29
lines changed

README.md

Lines changed: 27 additions & 27 deletions
Large diffs are not rendered by default.

cmd/zz_gen_cmd_dnshelp.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ func allDNSCodes() string {
3131
"clouddns",
3232
"cloudflare",
3333
"cloudns",
34+
"cloudru",
3435
"cloudxns",
3536
"conoha",
3637
"constellix",
@@ -516,6 +517,29 @@ func displayDNSHelp(w io.Writer, name string) error {
516517
ew.writeln()
517518
ew.writeln(`More information: https://go-acme.github.io/lego/dns/cloudns`)
518519

520+
case "cloudru":
521+
// generated from: providers/dns/cloudru/cloudru.toml
522+
ew.writeln(`Configuration for Cloud.ru.`)
523+
ew.writeln(`Code: 'cloudru'`)
524+
ew.writeln(`Since: 'v4.14.0'`)
525+
ew.writeln()
526+
527+
ew.writeln(`Credentials:`)
528+
ew.writeln(` - "CLOUDRU_KEY_ID": Key ID (login)`)
529+
ew.writeln(` - "CLOUDRU_SECRET": Key Secret`)
530+
ew.writeln(` - "CLOUDRU_SERVICE_INSTANCE_ID": Service Instance ID (parentId)`)
531+
ew.writeln()
532+
533+
ew.writeln(`Additional Configuration:`)
534+
ew.writeln(` - "CLOUDRU_HTTP_TIMEOUT": API request timeout`)
535+
ew.writeln(` - "CLOUDRU_POLLING_INTERVAL": Time between DNS propagation check`)
536+
ew.writeln(` - "CLOUDRU_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
537+
ew.writeln(` - "CLOUDRU_SEQUENCE_INTERVAL": Time between sequential requests`)
538+
ew.writeln(` - "CLOUDRU_TTL": The TTL of the TXT record used for the DNS challenge`)
539+
540+
ew.writeln()
541+
ew.writeln(`More information: https://go-acme.github.io/lego/dns/cloudru`)
542+
519543
case "cloudxns":
520544
// generated from: providers/dns/cloudxns/cloudxns.toml
521545
ew.writeln(`Configuration for CloudXNS.`)

docs/content/dns/zz_gen_cloudru.md

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
---
2+
title: "Cloud.ru"
3+
date: 2019-03-03T16:39:46+01:00
4+
draft: false
5+
slug: cloudru
6+
dnsprovider:
7+
since: "v4.14.0"
8+
code: "cloudru"
9+
url: "https://cloud.ru"
10+
---
11+
12+
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
13+
<!-- providers/dns/cloudru/cloudru.toml -->
14+
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
15+
16+
17+
Configuration for [Cloud.ru](https://cloud.ru).
18+
19+
20+
<!--more-->
21+
22+
- Code: `cloudru`
23+
- Since: v4.14.0
24+
25+
26+
Here is an example bash command using the Cloud.ru provider:
27+
28+
```bash
29+
CLOUDRU_SERVICE_INSTANCE_ID=ppp \
30+
CLOUDRU_KEY_ID=xxx \
31+
CLOUDRU_SECRET=yyy \
32+
lego --email [email protected] --dns cloudru --domains my.example.org run
33+
```
34+
35+
36+
37+
38+
## Credentials
39+
40+
| Environment Variable Name | Description |
41+
|-----------------------|-------------|
42+
| `CLOUDRU_KEY_ID` | Key ID (login) |
43+
| `CLOUDRU_SECRET` | Key Secret |
44+
| `CLOUDRU_SERVICE_INSTANCE_ID` | Service Instance ID (parentId) |
45+
46+
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
47+
More information [here]({{< ref "dns#configuration-and-credentials" >}}).
48+
49+
50+
## Additional Configuration
51+
52+
| Environment Variable Name | Description |
53+
|--------------------------------|-------------|
54+
| `CLOUDRU_HTTP_TIMEOUT` | API request timeout |
55+
| `CLOUDRU_POLLING_INTERVAL` | Time between DNS propagation check |
56+
| `CLOUDRU_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation |
57+
| `CLOUDRU_SEQUENCE_INTERVAL` | Time between sequential requests |
58+
| `CLOUDRU_TTL` | The TTL of the TXT record used for the DNS challenge |
59+
60+
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
61+
More information [here]({{< ref "dns#configuration-and-credentials" >}}).
62+
63+
64+
65+
66+
## More information
67+
68+
- [API documentation](https://cloud.ru/ru/docs/clouddns/ug/topics/api-ref.html)
69+
70+
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
71+
<!-- providers/dns/cloudru/cloudru.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, 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, 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, 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, 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, yandexcloud, zoneee, zonomi
141141
142142
More information: https://go-acme.github.io/lego/dns
143143
"""

providers/dns/cloudru/cloudru.go

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
// Package cloudru implements a DNS provider for solving the DNS-01 challenge using cloud.ru DNS.
2+
package cloudru
3+
4+
import (
5+
"context"
6+
"errors"
7+
"fmt"
8+
"net/http"
9+
"strconv"
10+
"sync"
11+
"time"
12+
13+
"github.com/go-acme/lego/v4/challenge/dns01"
14+
"github.com/go-acme/lego/v4/platform/config/env"
15+
"github.com/go-acme/lego/v4/providers/dns/cloudru/internal"
16+
)
17+
18+
// Environment variables names.
19+
const (
20+
envNamespace = "CLOUDRU_"
21+
22+
EnvServiceInstanceID = envNamespace + "SERVICE_INSTANCE_ID"
23+
EnvKeyID = envNamespace + "KEY_ID"
24+
EnvSecret = envNamespace + "SECRET"
25+
26+
EnvTTL = envNamespace + "TTL"
27+
EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT"
28+
EnvPollingInterval = envNamespace + "POLLING_INTERVAL"
29+
EnvSequenceInterval = envNamespace + "SEQUENCE_INTERVAL"
30+
EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT"
31+
)
32+
33+
// Config is used to configure the creation of the DNSProvider.
34+
type Config struct {
35+
ServiceInstanceID string
36+
KeyID string
37+
Secret string
38+
39+
PropagationTimeout time.Duration
40+
PollingInterval time.Duration
41+
SequenceInterval time.Duration
42+
HTTPClient *http.Client
43+
TTL int
44+
}
45+
46+
// NewDefaultConfig returns a default configuration for the DNSProvider.
47+
func NewDefaultConfig() *Config {
48+
return &Config{
49+
TTL: env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL),
50+
PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 5*time.Minute),
51+
PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, 5*time.Second),
52+
SequenceInterval: env.GetOrDefaultSecond(EnvSequenceInterval, dns01.DefaultPropagationTimeout),
53+
HTTPClient: &http.Client{
54+
Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second),
55+
},
56+
}
57+
}
58+
59+
type DNSProvider struct {
60+
config *Config
61+
client *internal.Client
62+
records map[string]*internal.Record
63+
recordsMu sync.Mutex
64+
}
65+
66+
// NewDNSProvider returns a DNSProvider instance configured for cloud.ru.
67+
// Credentials must be passed in the environment variables:
68+
// CLOUDRU_SERVICE_INSTANCE_ID, CLOUDRU_KEY_ID, and CLOUDRU_SECRET.
69+
func NewDNSProvider() (*DNSProvider, error) {
70+
values, err := env.Get(EnvServiceInstanceID, EnvKeyID, EnvSecret)
71+
if err != nil {
72+
return nil, fmt.Errorf("cloudru: %w", err)
73+
}
74+
75+
config := NewDefaultConfig()
76+
config.ServiceInstanceID = values[EnvServiceInstanceID]
77+
config.KeyID = values[EnvKeyID]
78+
config.Secret = values[EnvSecret]
79+
80+
return NewDNSProviderConfig(config)
81+
}
82+
83+
// NewDNSProviderConfig return a DNSProvider instance configured for cloud.ru.
84+
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
85+
if config == nil {
86+
return nil, errors.New("cloudru: the configuration of the DNS provider is nil")
87+
}
88+
89+
if config.ServiceInstanceID == "" || config.KeyID == "" || config.Secret == "" {
90+
return nil, errors.New("cloudru: some credentials information are missing")
91+
}
92+
93+
client := internal.NewClient(config.KeyID, config.Secret)
94+
95+
if config.HTTPClient != nil {
96+
client.HTTPClient = config.HTTPClient
97+
}
98+
99+
return &DNSProvider{
100+
config: config,
101+
client: client,
102+
records: make(map[string]*internal.Record),
103+
}, nil
104+
}
105+
106+
// Present creates a TXT record using the specified parameters.
107+
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
108+
info := dns01.GetChallengeInfo(domain, keyAuth)
109+
110+
authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN)
111+
if err != nil {
112+
return fmt.Errorf("cloudru: could not find zone for domain %q (%s): %w", domain, info.EffectiveFQDN, err)
113+
}
114+
115+
authZone = dns01.UnFqdn(authZone)
116+
117+
ctx, err := d.client.CreateAuthenticatedContext(context.Background())
118+
if err != nil {
119+
return fmt.Errorf("cloudru: %w", err)
120+
}
121+
122+
zone, err := d.getZoneInformationByName(ctx, d.config.ServiceInstanceID, authZone)
123+
if err != nil {
124+
return fmt.Errorf("cloudru: could not find zone information (ServiceInstanceID: %s, zone: %s): %w", d.config.ServiceInstanceID, authZone, err)
125+
}
126+
127+
record := internal.Record{
128+
Name: info.EffectiveFQDN,
129+
Type: "TXT",
130+
Values: []string{info.Value},
131+
TTL: strconv.Itoa(d.config.TTL),
132+
}
133+
134+
newRecord, err := d.client.CreateRecord(ctx, zone.ID, record)
135+
if err != nil {
136+
return fmt.Errorf("cloudru: could not create record: %w", err)
137+
}
138+
139+
d.recordsMu.Lock()
140+
d.records[token] = newRecord
141+
d.recordsMu.Unlock()
142+
143+
return nil
144+
}
145+
146+
// CleanUp removes a given record that was generated by Present.
147+
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
148+
info := dns01.GetChallengeInfo(domain, keyAuth)
149+
150+
d.recordsMu.Lock()
151+
record, ok := d.records[token]
152+
d.recordsMu.Unlock()
153+
154+
if !ok {
155+
return fmt.Errorf("cloudru: unknown recordID for %q", info.EffectiveFQDN)
156+
}
157+
158+
ctx, err := d.client.CreateAuthenticatedContext(context.Background())
159+
if err != nil {
160+
return fmt.Errorf("cloudru: %w", err)
161+
}
162+
163+
err = d.client.DeleteRecord(ctx, record.ZoneID, record.Name, "TXT")
164+
if err != nil {
165+
return fmt.Errorf("cloudru: %w", err)
166+
}
167+
168+
d.recordsMu.Lock()
169+
delete(d.records, token)
170+
d.recordsMu.Unlock()
171+
172+
return nil
173+
}
174+
175+
// Sequential All DNS challenges for this provider will be resolved sequentially.
176+
// Returns the interval between each iteration.
177+
func (d *DNSProvider) Sequential() time.Duration {
178+
return d.config.SequenceInterval
179+
}
180+
181+
// Timeout returns the timeout and interval to use when checking for DNS propagation.
182+
// Adjusting here to cope with spikes in propagation times.
183+
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
184+
return d.config.PropagationTimeout, d.config.PollingInterval
185+
}
186+
187+
func (d *DNSProvider) getZoneInformationByName(ctx context.Context, parentID, name string) (internal.Zone, error) {
188+
zs, err := d.client.GetZones(ctx, parentID)
189+
if err != nil {
190+
return internal.Zone{}, err
191+
}
192+
193+
for _, element := range zs {
194+
if element.Name == name {
195+
return element, nil
196+
}
197+
}
198+
199+
return internal.Zone{}, errors.New("could not find Zone record")
200+
}

providers/dns/cloudru/cloudru.toml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
Name = "Cloud.ru"
2+
Description = ''''''
3+
URL = "https://cloud.ru"
4+
Code = "cloudru"
5+
Since = "v4.14.0"
6+
7+
Example = '''
8+
CLOUDRU_SERVICE_INSTANCE_ID=ppp \
9+
CLOUDRU_KEY_ID=xxx \
10+
CLOUDRU_SECRET=yyy \
11+
lego --email [email protected] --dns cloudru --domains my.example.org run
12+
'''
13+
14+
[Configuration]
15+
[Configuration.Credentials]
16+
CLOUDRU_SERVICE_INSTANCE_ID = "Service Instance ID (parentId)"
17+
CLOUDRU_KEY_ID = "Key ID (login)"
18+
CLOUDRU_SECRET = "Key Secret"
19+
[Configuration.Additional]
20+
CLOUDRU_POLLING_INTERVAL = "Time between DNS propagation check"
21+
CLOUDRU_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation"
22+
CLOUDRU_TTL = "The TTL of the TXT record used for the DNS challenge"
23+
CLOUDRU_HTTP_TIMEOUT = "API request timeout"
24+
CLOUDRU_SEQUENCE_INTERVAL = "Time between sequential requests"
25+
26+
[Links]
27+
API = "https://cloud.ru/ru/docs/clouddns/ug/topics/api-ref.html"

0 commit comments

Comments
 (0)