sshfp-generator: New features and fixes

* Support for deleted hosts (cleanup records after configured time)
* Small fixes

Signed-off-by: Artur Marud <artur@status.im>
This commit is contained in:
Artur Marud 2022-06-27 17:47:07 +02:00
parent be748b44d6
commit bf6021c8bb
No known key found for this signature in database
GPG Key ID: 3A50153F6C80C7F9
9 changed files with 140 additions and 16 deletions

View File

@ -16,7 +16,8 @@ go build -mod vendor
Supported env variables: Supported env variables:
`DOMAIN_NAME` - Domain name which will be working on `DOMAIN_NAME` - Domain name which will be working on
`CF_TOKEN` - CloudFlare Token with write access to above domain `CF_TOKEN` - CloudFlare Token with write access to above domain
`CONSUL_TOKEN` - Consul Token with priviledges to read services `HOST_LIVENESS_TIMEOUT` - number in seconds after which host is
considered as removed and dns records can be deleted
It's possible to create json formatted config file (example in `testcfg`) It's possible to create json formatted config file (example in `testcfg`)

View File

@ -16,6 +16,7 @@ type Repository interface {
type Service interface { type Service interface {
FindHostByName(hostname string) (bool, error) FindHostByName(hostname string) (bool, error)
GetSSHFPRecordsForHost(hostname string) ([]*sshfp.SSHFPRecord, error) GetSSHFPRecordsForHost(hostname string) ([]*sshfp.SSHFPRecord, error)
DeleteSSHFPRecordsForHost(hostname string) error
CreateSSHFPRecord(hostname string, record sshfp.SSHFPRecord) (int, error) CreateSSHFPRecord(hostname string, record sshfp.SSHFPRecord) (int, error)
DeleteSSHFPRecord(hostname string, record sshfp.SSHFPRecord) error DeleteSSHFPRecord(hostname string, record sshfp.SSHFPRecord) error
UpdateSSHFPRecord(hostname string, record sshfp.SSHFPRecord) error UpdateSSHFPRecord(hostname string, record sshfp.SSHFPRecord) error

View File

@ -63,6 +63,23 @@ func (s *service) GetSSHFPRecordsForHost(hostname string) ([]*sshfp.SSHFPRecord,
return output, nil return output, nil
} }
func (s *service) DeleteSSHFPRecordsForHost(hostname string) error {
logrus.Debugf("cloudflare: DeleteSSHFPRecordsForHost: %s", hostname)
records, err := s.GetSSHFPRecordsForHost(hostname)
if err != nil {
return err
}
for _, record := range records {
err := s.DeleteSSHFPRecord(hostname, *record)
if err != nil {
return nil
}
}
return nil
}
func (s *service) CreateSSHFPRecord(hostname string, record sshfp.SSHFPRecord) (int, error) { func (s *service) CreateSSHFPRecord(hostname string, record sshfp.SSHFPRecord) (int, error) {
logrus.Infof("cloudflare: CreateSSHFPRecord: %+v", record) logrus.Infof("cloudflare: CreateSSHFPRecord: %+v", record)

View File

@ -1,7 +1,9 @@
package config package config
type Config struct { type Config struct {
ConsulToken string `json:"consulKey"` //ConsulToken string `json:"consulKey"`
CloudflareToken string `json:"cloudflareKey"` CloudflareToken string `json:"cloudflareKey"`
DomainName string `json:"domain"` DomainName string `json:"domain"`
HostTimeout int `json:"hostLivenessTimeout"`
LogLevel string `json:"logLevel"`
} }

View File

@ -3,6 +3,7 @@ package config
import ( import (
"errors" "errors"
"os" "os"
"strconv"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
@ -22,17 +23,27 @@ func (s *service) LoadConfig(fileName string) (*Config, error) {
if !exists { if !exists {
return nil, errors.New("cannot find env variable CF_TOKEN") return nil, errors.New("cannot find env variable CF_TOKEN")
} }
consulToken, exists := os.LookupEnv("CONSUL_TOKEN")
if !exists {
return nil, errors.New("cannot find env variable CONSUL_TOKEN")
}
domainaName, exists := os.LookupEnv("DOMAIN_NAME") domainaName, exists := os.LookupEnv("DOMAIN_NAME")
if exists { if !exists {
return nil, errors.New("cannot find env variable DOMAIN_NAME") return nil, errors.New("cannot find env variable DOMAIN_NAME")
} }
return &Config{ConsulToken: consulToken, CloudflareToken: cfToken, DomainName: domainaName}, nil hostTimeout, exists := os.LookupEnv("HOST_LIVENESS_TIMEOUT")
if !exists {
return nil, errors.New("cannot find env variable HOST_LIVENESS_TIMEOUT")
}
hostTimeoutInt, err := strconv.ParseInt(hostTimeout, 10, 32)
if err != nil {
return nil, errors.New("incorrect HOST_LIVENESS_TIMEOUT value")
}
logLevel, exists := os.LookupEnv(("LOG_LEVEL"))
if !exists {
return nil, errors.New("cannot find env variable LOG_LEVEL")
}
return &Config{CloudflareToken: cfToken, DomainName: domainaName, HostTimeout: int(hostTimeoutInt), LogLevel: logLevel}, nil
} }

22
main.go
View File

@ -6,17 +6,24 @@ import (
"infra-sshfp-cf/consul" "infra-sshfp-cf/consul"
"infra-sshfp-cf/sshfp" "infra-sshfp-cf/sshfp"
"infra-sshfp-cf/statestore" "infra-sshfp-cf/statestore"
"os"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
func main() { func main() {
//Debug loglevel //Debug loglevel
logrus.SetLevel(logrus.InfoLevel) logrus.SetLevel(logrus.DebugLevel)
//Create configuration components //Create configuration components
// Get config file name from args, if empty - try to configure from ENVs
var configFilename string = ""
if len(os.Args) > 1 {
configFilename = os.Args[1]
}
cfgService := config.NewService(config.NewFileRepository()) cfgService := config.NewService(config.NewFileRepository())
config, err := cfgService.LoadConfig("testcfg") config, err := cfgService.LoadConfig(configFilename)
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
@ -81,4 +88,15 @@ func main() {
} }
} }
//Purge old hosts
hosts, _ = statestore.GetStalledHosts(config.HostTimeout)
for _, host := range hosts {
err := cloudflare.DeleteSSHFPRecordsForHost(host)
if err != nil {
logrus.Fatalf("Cannot delete records for host: %s", host)
}
}
statestore.PurgeStalledHosts(config.HostTimeout)
} }

View File

@ -1,11 +1,18 @@
package statestore package statestore
import "time"
type Repository interface { type Repository interface {
GetModifyIndex(hostname string) (int, error) GetModifyIndex(hostname string) (int, error)
SetModifyIndex(hostname string, index int) error SetModifyIndex(hostname string, index int) error
DeleteHost(hostname string) error
DeleteHosts(hostnames []string) error
GetOutdatedHosts(time time.Duration) ([]string, error)
} }
type Service interface { type Service interface {
CheckIfModified(hostname string, index int) (bool, error) CheckIfModified(hostname string, index int) (bool, error)
SaveState(hostname string, index int) error SaveState(hostname string, index int) error
PurgeStalledHosts(timeTreshold int) error
GetStalledHosts(timeTreshold int) ([]string, error)
} }

View File

@ -3,12 +3,18 @@ package statestore
import ( import (
"encoding/json" "encoding/json"
"io/ioutil" "io/ioutil"
"time"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
type mapEntry struct {
ModifyIndex int
LastSeen time.Time
}
type mapRepository struct { type mapRepository struct {
db map[string]int db map[string]mapEntry
filename string filename string
} }
@ -21,7 +27,7 @@ func NewMapRepository(filename string) Repository {
return repo return repo
} }
db := make(map[string]int) db := make(map[string]mapEntry)
repo.db = db repo.db = db
err = repo.saveDatabase() err = repo.saveDatabase()
@ -34,16 +40,53 @@ func NewMapRepository(filename string) Repository {
func (r *mapRepository) GetModifyIndex(hostname string) (int, error) { func (r *mapRepository) GetModifyIndex(hostname string) (int, error) {
if value, ok := r.db[hostname]; ok { if value, ok := r.db[hostname]; ok {
return value, nil r.SetModifyIndex(hostname, value.ModifyIndex)
return value.ModifyIndex, nil
} }
return -1, nil return -1, nil
} }
func (r *mapRepository) GetOutdatedHosts(duration time.Duration) ([]string, error) {
var output []string
for k, v := range r.db {
if v.LastSeen.Add(duration).Before(time.Now()) {
output = append(output, k)
}
}
return output, nil
}
func (r *mapRepository) SetModifyIndex(hostname string, index int) error { func (r *mapRepository) SetModifyIndex(hostname string, index int) error {
r.db[hostname] = index if value, ok := r.db[hostname]; ok {
value.LastSeen = time.Now()
value.ModifyIndex = index
r.db[hostname] = value
} else {
value := new(mapEntry)
value.LastSeen = time.Now()
value.ModifyIndex = index
r.db[hostname] = *value
}
return r.saveDatabase() return r.saveDatabase()
} }
func (r *mapRepository) DeleteHost(hostname string) error {
logrus.Debugf("mapRepository: DeleteHost %s", hostname)
delete(r.db, hostname)
return r.saveDatabase()
}
func (r *mapRepository) DeleteHosts(hostnames []string) error {
for _, host := range hostnames {
r.DeleteHost(host)
}
return nil
}
func (r *mapRepository) openDatabase() error { func (r *mapRepository) openDatabase() error {
logrus.Infof("mapRepository: openDatabase %s", r.filename) logrus.Infof("mapRepository: openDatabase %s", r.filename)
content, err := ioutil.ReadFile(r.filename) content, err := ioutil.ReadFile(r.filename)

View File

@ -1,6 +1,10 @@
package statestore package statestore
import "github.com/sirupsen/logrus" import (
"time"
"github.com/sirupsen/logrus"
)
type service struct { type service struct {
r Repository r Repository
@ -30,3 +34,23 @@ func (s *service) SaveState(hostname string, index int) error {
return s.r.SetModifyIndex(hostname, index) return s.r.SetModifyIndex(hostname, index)
} }
func (s *service) GetStalledHosts(timeTreshold int) ([]string, error) {
return s.r.GetOutdatedHosts(time.Duration(timeTreshold) * time.Second)
}
func (s *service) PurgeStalledHosts(timeTreshold int) error {
logrus.Debugf("statestore: PurgeStalledHosts: %d", timeTreshold)
hosts, err := s.r.GetOutdatedHosts(time.Duration(timeTreshold) * time.Second)
if err != nil {
return err
}
logrus.Debugf("statestore: PurgeStalledHosts: %+v", hosts)
s.r.DeleteHosts(hosts)
return nil
}