Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Hover as DNS-style provider #1255

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 11 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,16 +54,16 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns).
| [DreamHost](https://go-acme.github.io/lego/dns/dreamhost/) | [Duck DNS](https://go-acme.github.io/lego/dns/duckdns/) | [Dyn](https://go-acme.github.io/lego/dns/dyn/) | [Dynu](https://go-acme.github.io/lego/dns/dynu/) |
| [EasyDNS](https://go-acme.github.io/lego/dns/easydns/) | [Exoscale](https://go-acme.github.io/lego/dns/exoscale/) | [External program](https://go-acme.github.io/lego/dns/exec/) | [Gandi Live DNS (v5)](https://go-acme.github.io/lego/dns/gandiv5/) |
| [Gandi](https://go-acme.github.io/lego/dns/gandi/) | [Glesys](https://go-acme.github.io/lego/dns/glesys/) | [Go Daddy](https://go-acme.github.io/lego/dns/godaddy/) | [Google Cloud](https://go-acme.github.io/lego/dns/gcloud/) |
| [Hetzner](https://go-acme.github.io/lego/dns/hetzner/) | [Hosting.de](https://go-acme.github.io/lego/dns/hostingde/) | [HTTP request](https://go-acme.github.io/lego/dns/httpreq/) | [HyperOne](https://go-acme.github.io/lego/dns/hyperone/) |
| [Internet Initiative Japan](https://go-acme.github.io/lego/dns/iij/) | [INWX](https://go-acme.github.io/lego/dns/inwx/) | [Joker](https://go-acme.github.io/lego/dns/joker/) | [Joohoi's ACME-DNS](https://go-acme.github.io/lego/dns/acme-dns/) |
| [Linode (v4)](https://go-acme.github.io/lego/dns/linode/) | [Liquid Web](https://go-acme.github.io/lego/dns/liquidweb/) | [LuaDNS](https://go-acme.github.io/lego/dns/luadns/) | [Manual](https://go-acme.github.io/lego/dns/manual/) |
| [MyDNS.jp](https://go-acme.github.io/lego/dns/mydnsjp/) | [MythicBeasts](https://go-acme.github.io/lego/dns/mythicbeasts/) | [Name.com](https://go-acme.github.io/lego/dns/namedotcom/) | [Namecheap](https://go-acme.github.io/lego/dns/namecheap/) |
| [Namesilo](https://go-acme.github.io/lego/dns/namesilo/) | [Netcup](https://go-acme.github.io/lego/dns/netcup/) | [Netlify](https://go-acme.github.io/lego/dns/netlify/) | [NIFCloud](https://go-acme.github.io/lego/dns/nifcloud/) |
| [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/) |
| [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/) | [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/) | [Vscale](https://go-acme.github.io/lego/dns/vscale/) | [Vultr](https://go-acme.github.io/lego/dns/vultr/) | [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/) | | |
| [Hetzner](https://go-acme.github.io/lego/dns/hetzner/) | [Hosting.de](https://go-acme.github.io/lego/dns/hostingde/) | [Hover](https://go-acme.github.io/lego/dns/hover/) | [HTTP request](https://go-acme.github.io/lego/dns/httpreq/) |
| [HyperOne](https://go-acme.github.io/lego/dns/hyperone/) | [Internet Initiative Japan](https://go-acme.github.io/lego/dns/iij/) | [INWX](https://go-acme.github.io/lego/dns/inwx/) | [Joker](https://go-acme.github.io/lego/dns/joker/) |
| [Joohoi's ACME-DNS](https://go-acme.github.io/lego/dns/acme-dns/) | [Linode (v4)](https://go-acme.github.io/lego/dns/linode/) | [Liquid Web](https://go-acme.github.io/lego/dns/liquidweb/) | [LuaDNS](https://go-acme.github.io/lego/dns/luadns/) |
| [Manual](https://go-acme.github.io/lego/dns/manual/) | [MyDNS.jp](https://go-acme.github.io/lego/dns/mydnsjp/) | [MythicBeasts](https://go-acme.github.io/lego/dns/mythicbeasts/) | [Name.com](https://go-acme.github.io/lego/dns/namedotcom/) |
| [Namecheap](https://go-acme.github.io/lego/dns/namecheap/) | [Namesilo](https://go-acme.github.io/lego/dns/namesilo/) | [Netcup](https://go-acme.github.io/lego/dns/netcup/) | [Netlify](https://go-acme.github.io/lego/dns/netlify/) |
| [NIFCloud](https://go-acme.github.io/lego/dns/nifcloud/) | [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/) | [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/) | [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/) | [Vscale](https://go-acme.github.io/lego/dns/vscale/) | [Vultr](https://go-acme.github.io/lego/dns/vultr/) |
| [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 -->
23 changes: 23 additions & 0 deletions cmd/zz_gen_cmd_dnshelp.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ func allDNSCodes() string {
"godaddy",
"hetzner",
"hostingde",
"hover",
"httpreq",
"hyperone",
"iij",
Expand Down Expand Up @@ -883,6 +884,28 @@ func displayDNSHelp(name string) error {
ew.writeln()
ew.writeln(`More information: https://go-acme.github.io/lego/dns/hostingde`)

case "hover":
// generated from: providers/dns/hover/hover.toml
ew.writeln(`Configuration for Hover.`)
ew.writeln(`Code: 'hover'`)
ew.writeln(`Since: 'v4.0.1'`)
ew.writeln()

ew.writeln(`Credentials:`)
ew.writeln(` - "HOVER_PASSFILE": /some/absolute/file.json`)
ew.writeln(` - "HOVER_PASSWORD": Password (plaintext)`)
ew.writeln(` - "HOVER_USERNAME": Username`)
ew.writeln()

ew.writeln(`Additional Configuration:`)
ew.writeln(` - "HOVER_HTTP_TIMEOUT": API request timeout`)
ew.writeln(` - "HOVER_POLLING_INTERVAL": Time between DNS propagation check`)
ew.writeln(` - "HOVER_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
ew.writeln(` - "HOVER_TTL": The TTL of the TXT record used for the DNS challenge`)

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

case "httpreq":
// generated from: providers/dns/httpreq/httpreq.toml
ew.writeln(`Configuration for HTTP request.`)
Expand Down
3 changes: 3 additions & 0 deletions providers/dns/dns_providers.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import (
"github.com/go-acme/lego/v4/providers/dns/godaddy"
"github.com/go-acme/lego/v4/providers/dns/hetzner"
"github.com/go-acme/lego/v4/providers/dns/hostingde"
"github.com/go-acme/lego/v4/providers/dns/hover"
"github.com/go-acme/lego/v4/providers/dns/httpreq"
"github.com/go-acme/lego/v4/providers/dns/hyperone"
"github.com/go-acme/lego/v4/providers/dns/iij"
Expand Down Expand Up @@ -161,6 +162,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) {
return hetzner.NewDNSProvider()
case "hostingde":
return hostingde.NewDNSProvider()
case "hover":
return hover.NewDNSProvider()
case "httpreq":
return httpreq.NewDNSProvider()
case "hyperone":
Expand Down
59 changes: 59 additions & 0 deletions providers/dns/hover/Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Hover DNS Provider

Hover has no formal API, so this mimicks a http client. Therefore, it uses a plaintext password,
which is generally a very bad idea. I would recommend where possible, and not protected by some
sort of configmap or such, to put those plaintext creds in a file, and reference that file from the
lego process

```bash
HOVER_PASSFILE=/private/hover.passwd lego ...
```

## Parameters

Config is done using either one or two environment variables (if you see different text in
`go run ./cmd/lego/ -- dnshelp -c hover`, this document needs update, `go run` output is correct)

### Authenticate using username and Plaintext password in environment

I really don't recommend using this, but if your username is "scott" and password "tiger", you should use:
```bash
export HOVER_USERNAME="scott"
export HOVER_PASSWORD="tiger"
lego -a -m chickenandpork@github.com -d example.com --dns hover run
```

Really, if you cannot protect visibility of the environment, use a file to further restrict access,
per next example.

### Authenticate using username and Plaintext password in file

This is the safer approach. Using a temporary file in a directory restricted to just the necessary
processes, or mapped into a container, this can give a higher level of protection on plaintext
passwords.

The parser for this auth is currently just JSON, but intends to fallback to other formats for
compatibility. There are so many formats to choose from, so there's intention to expand to 2 or 3
common formats for the most versatility. I get discouraged when using a tool that says "in order
to use this product, you need to learn an arcane object-oriented COBOL variant" or such. This
initial release is JSON, however.

```bash
export HOVER_PASSFILE=<some-temp-file>
echo '{"username": "scott", "plaintextpassword": "tiger"}' > "${HOVER_PASSFILE}"
lego -a -m chickenandpork@github.com -d example.com --dns hover run
```

This initial version of the underlying library does spam a lot of logs; this is intended to offer
debug details in case things go poorly. I expect the logs will silence over time.

## Testing

Testing is done similar to the recommended usage above:

```bash
HOVER_PASSFILE=$(mktemp)
echo '{"username": "scott", "plaintextpassword": "tiger"}' > "${HOVER_PASSFILE}" && \
go run ./cmd/lego/ -a -m chickenandpork@github.com -d example.com --dns hover run
rm -f "${HOVER_PASSFILE}"
```
129 changes: 129 additions & 0 deletions providers/dns/hover/hover.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// Package hover implements a DNS provider for solving the DNS-01 challenge using Hover DNS (past: "TuCows").
//
// This is based on attempting a python->go language conversion, and fit the smart parts from
// Dan Krause into the LeGo API. See https://gist.github.com/dankrause/5585907
package hover

import (
"errors"
"net/url"
"time"

"github.com/go-acme/lego/v3/challenge/dns01"
"github.com/go-acme/lego/v3/log"
"github.com/go-acme/lego/v3/platform/config/env"
"github.com/go-acme/lego/v4/providers/dns/hover/internal"
)

// Environment variables names.
const (
envNamespace = "HOVER_"

EnvDebug = envNamespace + "DEBUG"
EnvUsername = envNamespace + "USERNAME"
EnvPassword = envNamespace + "PASSWORD"
EnvFilename = envNamespace + "PASSFILE"

EnvTTL = envNamespace + "TTL"
EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT"
EnvPollingInterval = envNamespace + "POLLING_INTERVAL"
EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT"
)

// Config is used to configure the creation of the DNSProvider
type Config struct {
Username string
Password string
Server string

PropagationTimeout time.Duration
PollingInterval time.Duration
HTTPTimeout time.Duration
ttl uint
hover *internal.Client
parsed *url.URL
}

// NewDefaultConfig returns a default configuration for the DNSProvider
func NewDefaultConfig(u, p string) *Config {
return &Config{
ttl: uint(env.GetOrDefaultInt(EnvTTL, 3600)),
PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout),
PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval),
Username: u,
Password: p,
HTTPTimeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second),
}
}

// DNSProvider is an implementation of the challenge.Provider interface.
type DNSProvider struct {
config *Config
client *internal.Client
}

// NewDNSProvider returns a DNSProvider instance configured for Hover DNS.
// Credentials (username, password) must be passed in the environment variables; if they are
// undefined, look for a filename credential which will be parsed for the
// username/plaintextpassword (see hover_test.go for examples)
//
// NOTE: that the use of a password file is preferred to increase the burden to reap auth creds
func NewDNSProvider() (*DNSProvider, error) {
if values, err := env.Get(EnvUsername, EnvPassword); err != nil {
filename := ""
if v2, e2 := env.Get(EnvFilename); e2 == nil { // check whether we can fallback
filename = v2[EnvFilename]
} else {
return nil, err // nope; return original error
}
log.Infof("username (%s) and/or password (%s) environment variables not populated; reading from %s (%s)", EnvUsername, EnvPassword, EnvFilename, filename)

if pta, err := internal.ReadConfigFile(filename); err == nil {
log.Infof("username (%s) and/or password read from %s", pta.Username, filename)
return NewDNSProviderConfig(NewDefaultConfig(pta.Username, pta.PlaintextPassword))
}

// give up: no config provided: return a zero-initialized provider
return NewDNSProviderConfig(&Config{})
} else { // successful environment pull
return NewDNSProviderConfig(NewDefaultConfig(values[EnvUsername], values[EnvPassword]))
}
}

// NewDNSProviderConfig return a DNSProvider instance configured for Hover
func NewDNSProviderConfig(config *Config) (d *DNSProvider, err error) {
if config == nil {
return nil, errors.New("hover: the configuration of the DNS provider is nil")
}

if config.Username == "" {
return nil, errors.New("hover: incomplete credentials, missing Hover Username")
}
if config.Password == "" {
return nil, errors.New("hover: incomplete credentials, missing Hover Password")
}

config.hover = internal.NewClient(config.Username, config.Password, "", config.HTTPTimeout)

return &DNSProvider{config: config, client: config.hover}, nil
}

// Timeout returns the timeout and interval to use when checking for DNS propagation.
// Adjusting here to cope with spikes in propagation times.
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
return d.config.PropagationTimeout, d.config.PollingInterval
}

// Present creates a TXT record using the specified parameters
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
fqdn, value := dns01.GetRecord(domain, keyAuth)

return d.client.Upsert(dns01.UnFqdn(fqdn), domain, value, d.config.ttl)
}

// CleanUp removes the TXT record matching the specified parameters
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
fqdn, _ := dns01.GetRecord(domain, keyAuth)
log.Infof(`deleting token "%s" from domain "%s" (fqdn: %s) on auth "%s"`, token, domain, fqdn, keyAuth)
return d.client.Delete(dns01.UnFqdn(fqdn), domain)
}
28 changes: 28 additions & 0 deletions providers/dns/hover/hover.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
Name = "Hover"
Description = '''Hover DNS Hosting (tucows)'''
URL = "https://hover.com"
Code = "hover"
Since = "v4.0.1"

Example = '''
HOVER_USERNAME=scott \
HOVER_PASSWORD=tiger \
lego --dns hover --domains my.domain.com --email scott@example.com run

HOVER_PASSFILE=$(mktemp) \
echo '{"username": "scott", "password": "tiger"}' > ${HOVER_PASSFILE} \
lego --dns hover --domains my.domain.com --email scott@example.com run
'''

[Configuration]
[Configuration.Credentials]
HOVER_USERNAME = "Username"
HOVER_PASSWORD = "Password (plaintext)"
HOVER_PASSFILE = "/some/absolute/file.json"
[Configuration.Additional]
HOVER_POLLING_INTERVAL = "Time between DNS propagation check"
HOVER_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation"
HOVER_TTL = "The TTL of the TXT record used for the DNS challenge"
HOVER_HTTP_TIMEOUT = "API request timeout"

[Links]
Loading