Skip to content

Commit

Permalink
Add SafeDNS dns01 provider
Browse files Browse the repository at this point in the history
  • Loading branch information
Xiol committed Dec 16, 2021
1 parent 0f3a835 commit 30847bc
Show file tree
Hide file tree
Showing 8 changed files with 555 additions and 5 deletions.
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,11 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns).
| [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/) | [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/) | [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/) | [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/) | [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/) | [Sonic](https://go-acme.github.io/lego/dns/sonic/) | [Stackpath](https://go-acme.github.io/lego/dns/stackpath/) |
| [TransIP](https://go-acme.github.io/lego/dns/transip/) | [VegaDNS](https://go-acme.github.io/lego/dns/vegadns/) | [Versio.[nl/eu/uk]](https://go-acme.github.io/lego/dns/versio/) | [VinylDNS](https://go-acme.github.io/lego/dns/vinyldns/) |
| [Vscale](https://go-acme.github.io/lego/dns/vscale/) | [Vultr](https://go-acme.github.io/lego/dns/vultr/) | [WEDOS](https://go-acme.github.io/lego/dns/wedos/) | [Yandex](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/) | | |
| [RimuHosting](https://go-acme.github.io/lego/dns/rimuhosting/) | [SafeDNS](https://go-acme.github.io/lego/dns/safedns/) | [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/) | [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/) | [TransIP](https://go-acme.github.io/lego/dns/transip/) | [VegaDNS](https://go-acme.github.io/lego/dns/vegadns/) | [Versio.[nl/eu/uk]](https://go-acme.github.io/lego/dns/versio/) |
| [VinylDNS](https://go-acme.github.io/lego/dns/vinyldns/) | [Vscale](https://go-acme.github.io/lego/dns/vscale/) | [Vultr](https://go-acme.github.io/lego/dns/vultr/) | [WEDOS](https://go-acme.github.io/lego/dns/wedos/) |
| [Yandex](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/) | |

<!-- END DNS PROVIDERS LIST -->

Expand Down
21 changes: 21 additions & 0 deletions cmd/zz_gen_cmd_dnshelp.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ func allDNSCodes() string {
"rfc2136",
"rimuhosting",
"route53",
"safedns",
"sakuracloud",
"scaleway",
"selectel",
Expand Down Expand Up @@ -1829,6 +1830,26 @@ func displayDNSHelp(name string) error {
ew.writeln()
ew.writeln(`More information: https://go-acme.github.io/lego/dns/route53`)

case "safedns":
// generated from: providers/dns/safedns/safedns.toml
ew.writeln(`Configuration for SafeDNS.`)
ew.writeln(`Code: 'safedns'`)
ew.writeln(`Since: 'v4.6.0'`)
ew.writeln()

ew.writeln(`Credentials:`)
ew.writeln(` - "SAFEDNS_AUTH_TOKEN": Authentication token`)
ew.writeln()

ew.writeln(`Additional Configuration:`)
ew.writeln(` - "SAFEDNS_API_TIMEOUT": API request timeout in seconds`)
ew.writeln(` - "SAFEDNS_POLLING_INTERVAL": Time to wait for initial check`)
ew.writeln(` - "SAFEDNS_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
ew.writeln(` - "SAFEDNS_TTL": TXT record TTL`)

ew.writeln()
ew.writeln(`More information: https://go-acme.github.io/lego/dns/safedns`)

case "sakuracloud":
// generated from: providers/dns/sakuracloud/sakuracloud.toml
ew.writeln(`Configuration for Sakura Cloud.`)
Expand Down
62 changes: 62 additions & 0 deletions docs/content/dns/zz_gen_safedns.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
---
title: "SafeDNS"
date: 2019-03-03T16:39:46+01:00
draft: false
slug: safedns
---

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

Since: v4.6.0

Configuration for [SafeDNS](https://www.ukfast.co.uk/dns-hosting.html).


<!--more-->

- Code: `safedns`

Here is an example bash command using the SafeDNS provider:

```bash
SAFEDNS_AUTH_TOKEN=xxxxxx \
lego --email [email protected] --dns safedns --domains my.example.org run
```




## Credentials

| Environment Variable Name | Description |
|-----------------------|-------------|
| `SAFEDNS_AUTH_TOKEN` | Authentication token |

The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
More information [here](/lego/dns/#configuration-and-credentials).


## Additional Configuration

| Environment Variable Name | Description |
|--------------------------------|-------------|
| `SAFEDNS_API_TIMEOUT` | API request timeout in seconds |
| `SAFEDNS_POLLING_INTERVAL` | Time to wait for initial check |
| `SAFEDNS_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation |
| `SAFEDNS_TTL` | TXT record TTL |

The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
More information [here](/lego/dns/#configuration-and-credentials).




## More information

- [API documentation](https://developers.ukfast.io/documentation/safedns)

<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
<!-- providers/dns/safedns/safedns.toml -->
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
3 changes: 3 additions & 0 deletions providers/dns/dns_providers.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ import (
"github.com/go-acme/lego/v4/providers/dns/rfc2136"
"github.com/go-acme/lego/v4/providers/dns/rimuhosting"
"github.com/go-acme/lego/v4/providers/dns/route53"
"github.com/go-acme/lego/v4/providers/dns/safedns"
"github.com/go-acme/lego/v4/providers/dns/sakuracloud"
"github.com/go-acme/lego/v4/providers/dns/scaleway"
"github.com/go-acme/lego/v4/providers/dns/selectel"
Expand Down Expand Up @@ -269,6 +270,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) {
return rimuhosting.NewDNSProvider()
case "route53":
return route53.NewDNSProvider()
case "safedns":
return safedns.NewDNSProvider()
case "sakuracloud":
return sakuracloud.NewDNSProvider()
case "scaleway":
Expand Down
146 changes: 146 additions & 0 deletions providers/dns/safedns/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package safedns

import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"

"github.com/go-acme/lego/v4/challenge/dns01"
"github.com/go-acme/lego/v4/log"
)

const defaultBaseURL = "https://api.ukfast.io"

type txtResponse struct {
Data struct {
ID int `json:"id"`
} `json:"data"`
Meta struct {
Location string `json:"location"`
}
}

type recordType string

const (
typeTXT recordType = "TXT"
)

type record struct {
Name string `json:"name"`
Type recordType `json:"type"`
Content string `json:"content"`
TTL int `json:"ttl"`
}

type apiError struct {
Message string `json:"message"`
}

func (d *DNSProvider) addTxtRecord(fqdn, value string) (*txtResponse, error) {
zone, err := dns01.FindZoneByFqdn(dns01.ToFqdn(fqdn))
if err != nil {
return nil, fmt.Errorf("could not determine zone for domain: %q: %w", fqdn, err)
}

reqData := record{Name: dns01.UnFqdn(fqdn), Type: typeTXT, Content: fmt.Sprintf("\"%s\"", value), TTL: d.config.TTL}
body, err := json.Marshal(reqData)
if err != nil {
return nil, err
}

url := fmt.Sprintf("%s/safedns/v1/zones/%s/records", d.config.BaseURL, dns01.UnFqdn(zone))
req, err := d.newRequest(http.MethodPost, url, bytes.NewReader(body))
if err != nil {
return nil, err
}

log.Infof("safedns: creating record %+v at %s", reqData, url)

resp, err := d.config.HTTPClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()

if resp.StatusCode >= http.StatusBadRequest {
return nil, readError(req, resp)
}

content, err := io.ReadAll(resp.Body)
if err != nil {
return nil, errors.New(toUnreadableBodyMessage(req, content))
}

respData := &txtResponse{}
err = json.Unmarshal(content, respData)
if err != nil {
return nil, fmt.Errorf("%w: %s", err, toUnreadableBodyMessage(req, content))
}

log.Infof("safedns: created record with ID %d", respData.Data.ID)

return respData, nil
}

func (d *DNSProvider) removeTxtRecord(domain string, recordID int) error {
authZone, err := dns01.FindZoneByFqdn(dns01.ToFqdn(domain))
if err != nil {
return fmt.Errorf("safedns: could not determine zone for domain %q: %w", domain, err)
}

reqURL := fmt.Sprintf("%s/safedns/v1/zones/%s/records/%d", d.config.BaseURL, dns01.UnFqdn(authZone), recordID)
req, err := d.newRequest(http.MethodDelete, reqURL, nil)
if err != nil {
return err
}

log.Infof("safedns: cleaning up record %d at %s", recordID, reqURL)

resp, err := d.config.HTTPClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()

if resp.StatusCode >= 400 {
return readError(req, resp)
}

return nil
}

func (d *DNSProvider) newRequest(method, url string, body io.Reader) (*http.Request, error) {
req, err := http.NewRequest(method, url, body)
if err != nil {
return nil, err
}

req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")
req.Header.Set("Authorization", d.config.AuthToken)

return req, nil
}

func readError(req *http.Request, resp *http.Response) error {
content, err := io.ReadAll(resp.Body)
if err != nil {
return errors.New(toUnreadableBodyMessage(req, content))
}

var errInfo apiError
err = json.Unmarshal(content, &errInfo)
if err != nil {
return fmt.Errorf("safedns: unmarshaling error: %w: %s", err, toUnreadableBodyMessage(req, content))
}

return fmt.Errorf("safedns: HTTP %d: %s", resp.StatusCode, content)
}

func toUnreadableBodyMessage(req *http.Request, rawBody []byte) string {
return fmt.Sprintf("the request %s received a response with an invalid format: %q", req.URL, string(rawBody))
}
Loading

0 comments on commit 30847bc

Please sign in to comment.