Skip to content

Commit 07c4dae

Browse files
authored
Add DNS provider for Yandex 360 (go-acme#1975)
1 parent 01747e3 commit 07c4dae

File tree

16 files changed

+739
-4
lines changed

16 files changed

+739
-4
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,8 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns).
8383
| [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/) |
8484
| [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/) |
8585
| [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/) |
86-
| [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/) |
86+
| [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/) | [Zone.ee](https://go-acme.github.io/lego/dns/zoneee/) |
87+
| [Zonomi](https://go-acme.github.io/lego/dns/zonomi/) | | | |
8788

8889
<!-- END DNS PROVIDERS LIST -->
8990

cmd/zz_gen_cmd_dnshelp.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ func allDNSCodes() string {
134134
"websupport",
135135
"wedos",
136136
"yandex",
137+
"yandex360",
137138
"yandexcloud",
138139
"zoneee",
139140
"zonomi",
@@ -2700,6 +2701,27 @@ func displayDNSHelp(w io.Writer, name string) error {
27002701
ew.writeln()
27012702
ew.writeln(`More information: https://go-acme.github.io/lego/dns/yandex`)
27022703

2704+
case "yandex360":
2705+
// generated from: providers/dns/yandex360/yandex360.toml
2706+
ew.writeln(`Configuration for Yandex 360.`)
2707+
ew.writeln(`Code: 'yandex360'`)
2708+
ew.writeln(`Since: 'v4.14.0'`)
2709+
ew.writeln()
2710+
2711+
ew.writeln(`Credentials:`)
2712+
ew.writeln(` - "YANDEX360_OAUTH_TOKEN": The OAuth Token`)
2713+
ew.writeln(` - "YANDEX360_ORG_ID": The organization ID`)
2714+
ew.writeln()
2715+
2716+
ew.writeln(`Additional Configuration:`)
2717+
ew.writeln(` - "YANDEX360_HTTP_TIMEOUT": API request timeout`)
2718+
ew.writeln(` - "YANDEX360_POLLING_INTERVAL": Time between DNS propagation check`)
2719+
ew.writeln(` - "YANDEX360_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
2720+
ew.writeln(` - "YANDEX360_TTL": The TTL of the TXT record used for the DNS challenge`)
2721+
2722+
ew.writeln()
2723+
ew.writeln(`More information: https://go-acme.github.io/lego/dns/yandex360`)
2724+
27032725
case "yandexcloud":
27042726
// generated from: providers/dns/yandexcloud/yandexcloud.toml
27052727
ew.writeln(`Configuration for Yandex Cloud.`)
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
---
2+
title: "Yandex 360"
3+
date: 2019-03-03T16:39:46+01:00
4+
draft: false
5+
slug: yandex360
6+
dnsprovider:
7+
since: "v4.14.0"
8+
code: "yandex360"
9+
url: "https://360.yandex.ru"
10+
---
11+
12+
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
13+
<!-- providers/dns/yandex360/yandex360.toml -->
14+
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
15+
16+
17+
Configuration for [Yandex 360](https://360.yandex.ru).
18+
19+
20+
<!--more-->
21+
22+
- Code: `yandex360`
23+
- Since: v4.14.0
24+
25+
26+
Here is an example bash command using the Yandex 360 provider:
27+
28+
```bash
29+
YANDEX360_OAUTH_TOKEN=<your OAuth Token> \
30+
YANDEX360_ORG_ID=<your organization ID> \
31+
lego --email [email protected] --dns yandex360 --domains my.example.org run
32+
```
33+
34+
35+
36+
37+
## Credentials
38+
39+
| Environment Variable Name | Description |
40+
|-----------------------|-------------|
41+
| `YANDEX360_OAUTH_TOKEN` | The OAuth Token |
42+
| `YANDEX360_ORG_ID` | The organization ID |
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+
| `YANDEX360_HTTP_TIMEOUT` | API request timeout |
53+
| `YANDEX360_POLLING_INTERVAL` | Time between DNS propagation check |
54+
| `YANDEX360_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation |
55+
| `YANDEX360_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+
61+
62+
63+
## More information
64+
65+
- [API documentation](https://yandex.ru/dev/api360/doc/ref/DomainDNSService.html)
66+
67+
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
68+
<!-- providers/dns/yandex360/yandex360.toml -->
69+
<!-- 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, 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, 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
@@ -125,6 +125,7 @@ import (
125125
"github.com/go-acme/lego/v4/providers/dns/websupport"
126126
"github.com/go-acme/lego/v4/providers/dns/wedos"
127127
"github.com/go-acme/lego/v4/providers/dns/yandex"
128+
"github.com/go-acme/lego/v4/providers/dns/yandex360"
128129
"github.com/go-acme/lego/v4/providers/dns/yandexcloud"
129130
"github.com/go-acme/lego/v4/providers/dns/zoneee"
130131
"github.com/go-acme/lego/v4/providers/dns/zonomi"
@@ -375,6 +376,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) {
375376
return wedos.NewDNSProvider()
376377
case "yandex":
377378
return yandex.NewDNSProvider()
379+
case "yandex360":
380+
return yandex360.NewDNSProvider()
378381
case "yandexcloud":
379382
return yandexcloud.NewDNSProvider()
380383
case "zoneee":

providers/dns/safedns/internal/client.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ func (c *Client) AddRecord(ctx context.Context, zone string, record Record) (*Ad
5050
respData := &AddRecordResponse{}
5151
err = c.do(req, respData)
5252
if err != nil {
53-
return nil, fmt.Errorf("remove record: %w", err)
53+
return nil, fmt.Errorf("add record: %w", err)
5454
}
5555

5656
return respData, nil

providers/dns/yandex/yandex.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Package yandex implements a DNS provider for solving the DNS-01 challenge using Yandex.
1+
// Package yandex implements a DNS provider for solving the DNS-01 challenge using Yandex PDD.
22
package yandex
33

44
import (
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
package internal
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"encoding/json"
7+
"errors"
8+
"fmt"
9+
"io"
10+
"net/http"
11+
"net/url"
12+
"strconv"
13+
"time"
14+
15+
"github.com/go-acme/lego/v4/providers/dns/internal/errutils"
16+
)
17+
18+
const defaultBaseURL = "https://api360.yandex.net/"
19+
20+
type Client struct {
21+
oauthToken string
22+
orgID int64
23+
24+
baseURL *url.URL
25+
HTTPClient *http.Client
26+
}
27+
28+
func NewClient(oauthToken string, orgID int64) (*Client, error) {
29+
if oauthToken == "" {
30+
return nil, errors.New("OAuth token is required")
31+
}
32+
33+
if orgID == 0 {
34+
return nil, errors.New("orgID is required")
35+
}
36+
37+
baseURL, _ := url.Parse(defaultBaseURL)
38+
39+
return &Client{
40+
oauthToken: oauthToken,
41+
orgID: orgID,
42+
baseURL: baseURL,
43+
HTTPClient: &http.Client{Timeout: 10 * time.Second},
44+
}, nil
45+
}
46+
47+
// AddRecord Adds a DNS record.
48+
// POST https://api30.yandex.net/directory/v1/org/{orgId}/domains/{domain}/dns
49+
// https://yandex.ru/dev/api360/doc/ref/DomainDNSService/DomainDNSService_Create.html
50+
func (c Client) AddRecord(ctx context.Context, domain string, record Record) (*Record, error) {
51+
endpoint := c.baseURL.JoinPath("directory", "v1", "org", strconv.FormatInt(c.orgID, 10), "domains", domain, "dns")
52+
53+
req, err := newJSONRequest(ctx, http.MethodPost, endpoint, record)
54+
if err != nil {
55+
return nil, err
56+
}
57+
58+
var newRecord Record
59+
60+
err = c.do(req, &newRecord)
61+
if err != nil {
62+
return nil, err
63+
}
64+
65+
return &newRecord, nil
66+
}
67+
68+
// DeleteRecord Deletes a DNS record.
69+
// DELETE https://api360.yandex.net/directory/v1/org/{orgId}/domains/{domain}/dns/{recordId}
70+
// https://yandex.ru/dev/api360/doc/ref/DomainDNSService/DomainDNSService_Delete.html
71+
func (c Client) DeleteRecord(ctx context.Context, domain string, recordID int64) error {
72+
endpoint := c.baseURL.JoinPath("directory", "v1", "org", strconv.FormatInt(c.orgID, 10), "domains", domain, "dns", strconv.FormatInt(recordID, 10))
73+
74+
req, err := newJSONRequest(ctx, http.MethodDelete, endpoint, nil)
75+
if err != nil {
76+
return err
77+
}
78+
79+
return c.do(req, nil)
80+
}
81+
82+
func (c Client) do(req *http.Request, result any) error {
83+
req.Header.Set("Authorization", "OAuth "+c.oauthToken)
84+
85+
resp, err := c.HTTPClient.Do(req)
86+
if err != nil {
87+
return errutils.NewHTTPDoError(req, err)
88+
}
89+
90+
defer func() { _ = resp.Body.Close() }()
91+
92+
if resp.StatusCode/100 != 2 {
93+
return parseError(req, resp)
94+
}
95+
96+
if result == nil {
97+
return nil
98+
}
99+
100+
raw, err := io.ReadAll(resp.Body)
101+
if err != nil {
102+
return errutils.NewReadResponseError(req, resp.StatusCode, err)
103+
}
104+
105+
err = json.Unmarshal(raw, result)
106+
if err != nil {
107+
return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err)
108+
}
109+
110+
return nil
111+
}
112+
113+
func newJSONRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) {
114+
buf := new(bytes.Buffer)
115+
116+
if payload != nil {
117+
err := json.NewEncoder(buf).Encode(payload)
118+
if err != nil {
119+
return nil, fmt.Errorf("failed to create request JSON body: %w", err)
120+
}
121+
}
122+
123+
req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), buf)
124+
if err != nil {
125+
return nil, fmt.Errorf("unable to create request: %w", err)
126+
}
127+
128+
req.Header.Set("Accept", "application/json")
129+
130+
if payload != nil {
131+
req.Header.Set("Content-Type", "application/json")
132+
}
133+
134+
return req, nil
135+
}
136+
137+
func parseError(req *http.Request, resp *http.Response) error {
138+
raw, _ := io.ReadAll(resp.Body)
139+
140+
var apiErr APIError
141+
err := json.Unmarshal(raw, &apiErr)
142+
if err != nil {
143+
return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw)
144+
}
145+
146+
return fmt.Errorf("[status code: %d] %w", resp.StatusCode, apiErr)
147+
}

0 commit comments

Comments
 (0)