From 63b9c1a8d8efa46f55aa6de10357181cc0bc7bfc Mon Sep 17 00:00:00 2001 From: DataHearth Date: Thu, 27 May 2021 12:33:02 +0200 Subject: [PATCH] add token based providers --- main.go | 9 +---- pkg/provider/main.go | 74 +++++++++++++++++++++++++++++++++++------- pkg/utils/config.go | 33 ------------------- pkg/utils/types.go | 40 ++++++++++++++++++++--- pkg/utils/utils.go | 45 ------------------------- pkg/watcher/watcher.go | 26 +++++++++++++-- 6 files changed, 122 insertions(+), 105 deletions(-) delete mode 100644 pkg/utils/config.go diff --git a/main.go b/main.go index 7313c6a..e81965a 100644 --- a/main.go +++ b/main.go @@ -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) diff --git a/pkg/provider/main.go b/pkg/provider/main.go index 33067f8..fed8fce 100644 --- a/pkg/provider/main.go +++ b/pkg/provider/main.go @@ -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 +} diff --git a/pkg/utils/config.go b/pkg/utils/config.go deleted file mode 100644 index 8d681a6..0000000 --- a/pkg/utils/config.go +++ /dev/null @@ -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", -} diff --git a/pkg/utils/types.go b/pkg/utils/types.go index 96acc9f..1887045 100644 --- a/pkg/utils/types.go +++ b/pkg/utils/types.go @@ -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", +} diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 3b8811c..feb7fee 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -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 -} diff --git a/pkg/watcher/watcher.go b/pkg/watcher/watcher.go index 5aab49f..9879507 100644 --- a/pkg/watcher/watcher.go +++ b/pkg/watcher/watcher.go @@ -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 +}