add token based providers

This commit is contained in:
DataHearth 2021-05-27 12:33:02 +02:00
parent 21b55d8a0a
commit 63b9c1a8d8
No known key found for this signature in database
GPG Key ID: E88FD356ACC5F3C4
6 changed files with 122 additions and 105 deletions

View File

@ -12,13 +12,6 @@ import (
"github.com/sirupsen/logrus"
)
var (
ErrSbsLen = errors.New("subdomains len is 0")
ErrInvalidProvider = errors.New("invalid provider name")
ErrWatchersConfigLen = errors.New("watcher configuration needs at least one [watcherd] and [providerd] configuration")
ErrWatcherCreationLen = errors.New("no valid watchers were created. Checkout [watchers] configuration and its [providers] configuration")
)
// Start create a new instance of ddns-client
func Start(logger logrus.FieldLogger, config utils.ClientConfig) error {
log := logger.WithFields(logrus.Fields{
@ -38,7 +31,7 @@ func Start(logger logrus.FieldLogger, config utils.ClientConfig) error {
}
if len(ws) == 0 {
return ErrWatcherCreationLen
return errors.New("no valid watchers were created. Checkout [watchers] configuration and its [providers] configuration")
}
sigc := make(chan os.Signal, 1)

View File

@ -1,8 +1,9 @@
package provider
import (
"errors"
"fmt"
"io/ioutil"
"net"
"net/http"
"strings"
@ -10,9 +11,10 @@ import (
"github.com/sirupsen/logrus"
)
// Provider is the default interface for all providers
type Provider interface {
UpdateSubdomains(ip string) error
updateSubdomain(subdomain, ip string) error
retrieveSubdomainIP(addr string) (string, error)
}
type provider struct {
@ -22,6 +24,7 @@ type provider struct {
url string
}
// NewProvider creates a new instance of the `Provider` interface
func NewProvider(logger logrus.FieldLogger, config utils.Config, url, name string) (Provider, error) {
if logger == nil {
return nil, utils.ErrNilLogger
@ -45,9 +48,11 @@ func NewProvider(logger logrus.FieldLogger, config utils.Config, url, name strin
}, nil
}
// UpdateSubdomains will watch for every defined subdomains if they need an update.
// If so, it'll trigger an update
func (p *provider) UpdateSubdomains(srvIP string) error {
for _, sb := range p.config.Subdomains {
ip, err := utils.RetrieveSubdomainIP(sb)
ip, err := p.retrieveSubdomainIP(sb)
if err != nil {
return err
}
@ -63,14 +68,11 @@ func (p *provider) UpdateSubdomains(srvIP string) error {
"subdomain": sb,
}).Infoln("IP addresses doesn't match. Updating subdomain's ip...")
if err := p.updateSubdomain(sb, srvIP); err != nil {
if err != utils.ErrReadBody && err != utils.ErrWrongStatusCode {
return err
}
p.logger.WithError(err).WithFields(logrus.Fields{
"component": "UpdateSubdomains",
"subdomain": sb,
"new-ip": srvIP,
}).Warnln("failed to update subdomain ip")
}).Errorln("failed to update subdomain ip")
}
}
@ -78,8 +80,13 @@ func (p *provider) UpdateSubdomains(srvIP string) error {
}
func (p *provider) updateSubdomain(subdomain, ip string) error {
tokenBased := p.config.Token != "" && (p.config.Username == "" && p.config.Password == "")
newURL := strings.ReplaceAll(p.url, "SUBDOMAIN", subdomain)
newURL = strings.ReplaceAll(newURL, "NEWIP", ip)
if tokenBased {
newURL = strings.ReplaceAll(newURL, "TOKEN", p.config.Token)
}
logger := p.logger.WithFields(logrus.Fields{
"component": "UpdateIP",
"updated-url": newURL,
@ -91,23 +98,25 @@ func (p *provider) updateSubdomain(subdomain, ip string) error {
if err != nil {
return utils.ErrCreateNewRequest
}
req.SetBasicAuth(p.config.Username, p.config.Password)
if !tokenBased {
req.SetBasicAuth(p.config.Username, p.config.Password)
}
logger.Debugln("calling DDNS provider for subdomain update")
c := new(http.Client)
resp, err := c.Do(req)
if err != nil {
return utils.ErrUpdateRequest
return err
}
if resp.ContentLength != 0 {
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
return utils.ErrReadBody
return err
}
if !strings.Contains(string(b), "good "+ip) && !strings.Contains(string(b), "nochg "+ip) {
return errors.New("failed to update subdomain ip. Error: " + string(b))
if err := p.checkResponse(b, tokenBased, ip); err != nil {
return err
}
}
@ -117,3 +126,44 @@ func (p *provider) updateSubdomain(subdomain, ip string) error {
return nil
}
func (p *provider) retrieveSubdomainIP(addr string) (string, error) {
ips, err := net.LookupIP(addr)
if err != nil {
return "", err
}
if len(ips) != 1 {
return "", utils.ErrIpLenght
}
ip := ips[0].String()
if strings.Contains(ip, ":") {
ip, _, err = net.SplitHostPort(ip)
if err != nil {
return "", utils.ErrSplitAddr
}
}
return ip, nil
}
func (p *provider) checkResponse(body []byte, tokenBased bool, ip string) error {
var invalidResponse error
if tokenBased {
if !strings.Contains(string(body), "OK") {
if strings.Contains(string(body), "KO") {
invalidResponse = fmt.Errorf("invalid body response.\n Body response: %v", string(body))
} else {
invalidResponse = fmt.Errorf("unknown body response. Please fill a issue if you think this is an error.\n Body response: %v", string(body))
}
}
} else {
if !strings.Contains(string(body), "good "+ip) && !strings.Contains(string(body), "nochg "+ip) {
invalidResponse = fmt.Errorf("invalid body response.\n Body response: %v", string(body))
}
}
return invalidResponse
}

View File

@ -1,33 +0,0 @@
package utils
type ClientConfig struct {
Logger Logger `mapstructure:"logger"`
Watchers []Watcher `mapstructure:"watchers"`
UpdateTime int `mapstructure:"update-time,omitempty"`
PendingDnsPropagation int `mapstructure:"pending-dns-propagation,omitempty"`
WebIP string `mapstructure:"web-ip,omitempty"`
}
type Logger struct {
Level string `mapstructure:"level"`
DisableTimestamp bool `mapstructure:"disable-timestamp,omitempty"`
DisableColor bool `mapstructure:"disable-color,omitempty"`
}
type Watcher struct {
Name string `yaml:"name"`
URL string `yaml:"url,omitempty"`
Config []Config `yaml:"config"`
}
type Config struct {
Username string `yaml:"username"`
Password string `yaml:"password"`
Subdomains []string `yaml:"subdomains"`
}
var DefaultURLs = map[string]string{
"ovh": "http://www.ovh.com/nic/update?system=dyndns&hostname=SUBDOMAIN&myip=NEWIP",
"google": "https://domains.google.com/nic/update?hostname=SUBDOMAIN&myip=NEWIP",
"webIP": "http://dynamicdns.park-your-domain.com/getip",
}

View File

@ -4,7 +4,7 @@ import (
"errors"
)
// * Errors
// ** ERRORS ** //
var (
// ErrNilLogger is thrown when the parameter logger is nil
ErrNilLogger = errors.New("logger is mandatory")
@ -18,14 +18,10 @@ var (
ErrSplitAddr = errors.New("can't split subdomain remote IP address")
// ErrCreateNewRequest is thrown when http request creation failed
ErrCreateNewRequest = errors.New("can't create http request")
// ErrUpdateRequest is thrown when the update request failed
ErrUpdateRequest = errors.New("failed to set new IP address")
// ErrInvalidURL is thrown when user does not provide a URL and it does not exist in default urls
ErrInvalidURL = errors.New("no url was provided")
// ErrInvalidName is thrown when provider name was not provided
ErrInvalidName = errors.New("no provider name was provided")
// ErrReadBody is thrown when body response can't be parsed
ErrReadBody = errors.New("failed to read response body")
// ErrNilWatcher is thrown when no watcher config was provided
ErrNilWatcher = errors.New("watcher is mandatory")
// ErrIpLength is thrown when subdomain no or multiples remote IP address
@ -33,3 +29,37 @@ var (
// ErrNilConfig is thrown when an empty config is provided
ErrNilConfig = errors.New("config is mandatory")
)
// ** CONFIGURATION ** //
type ClientConfig struct {
Logger Logger `mapstructure:"logger"`
Watchers []Watcher `mapstructure:"watchers"`
UpdateTime int `mapstructure:"update-time,omitempty"`
WebIP string `mapstructure:"web-ip,omitempty"`
}
type Logger struct {
Level string `mapstructure:"level"`
DisableTimestamp bool `mapstructure:"disable-timestamp,omitempty"`
DisableColor bool `mapstructure:"disable-color,omitempty"`
}
type Watcher struct {
Name string `yaml:"name"`
URL string `yaml:"url,omitempty"`
Config []Config `yaml:"config"`
}
type Config struct {
Username string `yaml:"username,omitempty"`
Password string `yaml:"password,omitempty"`
Token string `yaml:"password,omitempty"`
Subdomains []string `yaml:"subdomains"`
}
var DefaultURLs = map[string]string{
"ovh": "http://www.ovh.com/nic/update?system=dyndns&hostname=SUBDOMAIN&myip=NEWIP",
"google": "https://domains.google.com/nic/update?hostname=SUBDOMAIN&myip=NEWIP",
"duckdns": "https://duckdns.org/update/SUBDOMAIN/TOKEN[/NEWIP",
"webIP": "http://dynamicdns.park-your-domain.com/getip",
}

View File

@ -1,11 +1,6 @@
package utils
import (
"io/ioutil"
"net"
"net/http"
"strings"
"github.com/sirupsen/logrus"
"github.com/spf13/viper"
)
@ -55,43 +50,3 @@ func SetupLogger(logger *logrus.Logger) {
DisableTimestamp: timestamp,
})
}
// RetrieveServerIP will use the defined web-ip service to get the server public address
func RetrieveServerIP(webIP string) (string, error) {
resp, err := http.Get(webIP)
if err != nil {
return "", ErrGetServerIP
}
if resp.StatusCode != 200 {
return "", ErrWrongStatusCode
}
d, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", ErrParseHTTPBody
}
return string(d), nil
}
// RetrieveSubdomainIP will retrieve the subdomain IP
func RetrieveSubdomainIP(addr string) (string, error) {
ips, err := net.LookupIP(addr)
if err != nil {
return "", err
}
if len(ips) != 1 {
return "", ErrIpLenght
}
ip := ips[0].String()
if strings.Contains(ip, ":") {
ip, _, err = net.SplitHostPort(ip)
if err != nil {
return "", ErrSplitAddr
}
}
return ip, nil
}

View File

@ -1,6 +1,8 @@
package watcher
import (
"io/ioutil"
"net/http"
"time"
"github.com/datahearth/ddnsclient/pkg/provider"
@ -10,6 +12,8 @@ import (
type Watcher interface {
Run(*time.Ticker, chan struct{}, chan error)
runDDNSCheck() error
retrieveServerIP() (string, error)
}
type watcher struct {
@ -20,7 +24,7 @@ type watcher struct {
providerName string
}
// NewWatcher creates a watcher a given provider config
// NewWatcher creates a new instance of the `Watcher` interface
func NewWatcher(logger logrus.FieldLogger, w *utils.Watcher, webIP string) (Watcher, error) {
if logger == nil {
return nil, utils.ErrNilLogger
@ -51,6 +55,7 @@ func NewWatcher(logger logrus.FieldLogger, w *utils.Watcher, webIP string) (Watc
}, nil
}
// Run will trigger a subdomain update every X seconds (defined in config `update-time`)
func (w *watcher) Run(t *time.Ticker, chClose chan struct{}, chErr chan error) {
logger := w.logger.WithField("component", "Run")
@ -81,7 +86,7 @@ func (w *watcher) runDDNSCheck() error {
logger.Infof("Starting [%s] DDNS check...\n", w.providerName)
logger.Debugln("Checking server IP...")
srvIP, err := utils.RetrieveServerIP(w.webIP)
srvIP, err := w.retrieveServerIP()
if err != nil {
return err
}
@ -95,3 +100,20 @@ func (w *watcher) runDDNSCheck() error {
logger.Infof("[%s] DDNS check finished\n", w.providerName)
return nil
}
func (w *watcher) retrieveServerIP() (string, error) {
resp, err := http.Get(w.webIP)
if err != nil {
return "", utils.ErrGetServerIP
}
if resp.StatusCode != 200 {
return "", utils.ErrWrongStatusCode
}
d, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", utils.ErrParseHTTPBody
}
return string(d), nil
}