From 4a67e88e53c98468674fb391156b5e91b967a88c Mon Sep 17 00:00:00 2001 From: sapslaj Date: Mon, 15 Jan 2024 00:27:54 -0500 Subject: [PATCH] add hosts file provider missing a bunch of tests rn but whatever i'll fix it later lol --- .golangci.yml | 1 + config/config.go | 12 ++++ go.mod | 1 + go.sum | 9 +++ provider/hosts_file/hosts_file.go | 112 ++++++++++++++++++++++++++++++ 5 files changed, 135 insertions(+) create mode 100644 provider/hosts_file/hosts_file.go diff --git a/.golangci.yml b/.golangci.yml index 9f53c1b..a4cff72 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -62,6 +62,7 @@ linters-settings: max-complexity: 17 funlen: lines: 100 + statements: 50 gci: sections: - standard diff --git a/config/config.go b/config/config.go index 7bb966a..a8bf7ff 100644 --- a/config/config.go +++ b/config/config.go @@ -14,6 +14,7 @@ import ( "github.com/sapslaj/zonepop/provider" "github.com/sapslaj/zonepop/provider/aws" custom_provider "github.com/sapslaj/zonepop/provider/custom" + hostsfile "github.com/sapslaj/zonepop/provider/hosts_file" "github.com/sapslaj/zonepop/source" custom_source "github.com/sapslaj/zonepop/source/custom" "github.com/sapslaj/zonepop/source/vyos" @@ -201,6 +202,17 @@ func (c *luaConfig) Providers() ([]provider.Provider, error) { reverseFilterFunc, ) } + case "hosts_file": + var hfConfig hostsfile.HostsFileProviderConfig + err = gluamapper.Map(providerConfig, &hfConfig) + if err != nil { + providerLogger.Errorw("error configuring provider", "err", err) + return providers, err + } + provider, err = hostsfile.NewHostsFileProvider( + hfConfig, + forwardFilterFunc, + ) } if err != nil { diff --git a/go.mod b/go.mod index 19c416d..5a0641e 100644 --- a/go.mod +++ b/go.mod @@ -27,6 +27,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/sts v1.18.5 // indirect github.com/aws/smithy-go v1.13.5 // indirect github.com/benbjohnson/clock v1.1.0 // indirect + github.com/bramvdbogaerde/go-scp v1.2.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect diff --git a/go.sum b/go.sum index 32a0e4f..c2c2182 100644 --- a/go.sum +++ b/go.sum @@ -26,6 +26,8 @@ github.com/aws/smithy-go v1.13.5 h1:hgz0X/DX0dGqTYpGALqXJoRKRj5oQ7150i5FdTePzO8= github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/bramvdbogaerde/go-scp v1.2.1 h1:BKTqrqXiQYovrDlfuVFaEGz0r4Ou6EED8L7jCXw6Buw= +github.com/bramvdbogaerde/go-scp v1.2.1/go.mod h1:s4ZldBoRAOgUg8IrRP2Urmq5qqd2yPXQTPshACY8vQ0= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -60,12 +62,19 @@ go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= +golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= diff --git a/provider/hosts_file/hosts_file.go b/provider/hosts_file/hosts_file.go new file mode 100644 index 0000000..ca07950 --- /dev/null +++ b/provider/hosts_file/hosts_file.go @@ -0,0 +1,112 @@ +package hostsfile + +import ( + "context" + "fmt" + "io/fs" + "os" + "strconv" + "strings" + + scp "github.com/bramvdbogaerde/go-scp" + "go.uber.org/zap" + + "github.com/sapslaj/zonepop/config/configtypes" + "github.com/sapslaj/zonepop/endpoint" + "github.com/sapslaj/zonepop/pkg/log" + "github.com/sapslaj/zonepop/pkg/sshconnection" + "github.com/sapslaj/zonepop/pkg/utils" + "github.com/sapslaj/zonepop/provider" +) + +type HostsFileProviderConfigSSH struct { + Host string + Username string + Password string +} + +type HostsFileProviderConfig struct { + File string + Permissions string + SSH HostsFileProviderConfigSSH +} + +type hostsFileProvider struct { + config HostsFileProviderConfig + logger *zap.Logger + forwardLookupFilter configtypes.EndpointFilterFunc +} + +func NewHostsFileProvider( + providerConfig HostsFileProviderConfig, + forwardLookupFilter configtypes.EndpointFilterFunc, +) (provider.Provider, error) { + if providerConfig.Permissions == "" { + providerConfig.Permissions = "0644" + } + p := &hostsFileProvider{ + config: providerConfig, + logger: log.MustNewLogger().Named("hosts_file_provider"), + forwardLookupFilter: forwardLookupFilter, + } + return p, nil +} + +func (p *hostsFileProvider) UpdateEndpoints(ctx context.Context, endpoints []*endpoint.Endpoint) error { + // TODO: reverse lookup? + forwardEndpoints := utils.Filter(p.forwardLookupFilter, endpoints) + result := p.endpointsToFile(forwardEndpoints) + var err error + if p.config.SSH.Host != "" { + err = p.saveToSSH(ctx, result) + if err != nil { + p.logger.Sugar().Errorw("failed to save to local file", "err", err) + } + } else { + err = p.saveToLocalFile(ctx, result) + if err != nil { + p.logger.Sugar().Errorw("failed to save to remote SSH file", "err", err) + } + } + return err +} + +func (p *hostsFileProvider) endpointsToFile(endpoints []*endpoint.Endpoint) string { + p.logger.Sugar().Infof("generating %d host file entries", len(endpoints)) + var s strings.Builder + s.WriteString("# Generated by ZonePop\n") + for _, endpoint := range endpoints { + for _, ipv4 := range endpoint.IPv4s { + s.WriteString(fmt.Sprintf("%s\t%s\n", ipv4, endpoint.Hostname)) + } + for _, ipv6 := range endpoint.IPv6s { + s.WriteString(fmt.Sprintf("%s\t%s\n", ipv6, endpoint.Hostname)) + } + } + return s.String() +} + +func (p *hostsFileProvider) saveToLocalFile(ctx context.Context, result string) error { + p.logger.Sugar().Infof("saving hosts file to (local) %s with permissions %s", p.config.File, p.config.Permissions) + perm, err := strconv.ParseInt(p.config.Permissions, 8, 0) + if err != nil { + return err + } + p.logger.Sugar().Infof("perm: %s %d", p.config.Permissions, fs.FileMode(perm)) + return os.WriteFile(p.config.File, []byte(result), fs.FileMode(perm)) +} + +func (p *hostsFileProvider) saveToSSH(ctx context.Context, result string) error { + p.logger.Sugar().Infof("saving hosts file to (SSH) %s:%s with permissions %s", p.config.SSH.Host, p.config.File, p.config.Permissions) + conn, err := sshconnection.Connect(p.config.SSH.Host, p.config.SSH.Username, p.config.SSH.Password) + if err != nil { + return err + } + defer conn.Disconnect() + client, err := scp.NewClientBySSH(conn.Client) + if err != nil { + return err + } + reader := strings.NewReader(result) + return client.CopyFile(ctx, reader, p.config.File, p.config.Permissions) +}