From dc1f71da8a06363fa6db719880abac3e2d2902b4 Mon Sep 17 00:00:00 2001 From: Stefan Majer Date: Mon, 4 Sep 2023 15:39:51 +0200 Subject: [PATCH] Add feature to specify the allowed dhcp cidrs --- internal/leases/leases.go | 7 +---- internal/leases/leases_test.go | 2 +- internal/leases/parser.go | 11 +++++-- internal/leases/parser_test.go | 2 +- internal/leases/types.go | 9 ------ internal/reporter/reporter.go | 50 ++++++++++++++++++++++++-------- main.go | 8 +++-- {domain => pkg/config}/config.go | 15 +++++++++- 8 files changed, 69 insertions(+), 35 deletions(-) rename {domain => pkg/config}/config.go (89%) diff --git a/internal/leases/leases.go b/internal/leases/leases.go index 4e547a8..bed8060 100644 --- a/internal/leases/leases.go +++ b/internal/leases/leases.go @@ -1,7 +1,6 @@ package leases import ( - "fmt" "os" "time" ) @@ -32,11 +31,7 @@ func (l Leases) LatestByMac() map[string]Lease { func ReadLeases(leaseFile string) (Leases, error) { leasesContent := mustRead(leaseFile) - leases, err := Parse(leasesContent) - if err != nil { - return nil, fmt.Errorf("could not parse leases file:%w", err) - } - return leases, nil + return parse(leasesContent) } func mustRead(name string) string { diff --git a/internal/leases/leases_test.go b/internal/leases/leases_test.go index f33d0c5..4e37c00 100644 --- a/internal/leases/leases_test.go +++ b/internal/leases/leases_test.go @@ -9,7 +9,7 @@ import ( func TestFilterActive(t *testing.T) { assert := assert.New(t) - l, err := Parse(LEASES_CONTENT) + l, err := parse(LEASES_CONTENT) assert.NoError(err) assert.Equal(Leases{}, l.FilterActive()) } diff --git a/internal/leases/parser.go b/internal/leases/parser.go index e96c3ac..02529a1 100644 --- a/internal/leases/parser.go +++ b/internal/leases/parser.go @@ -1,6 +1,7 @@ package leases import ( + "errors" "regexp" "time" ) @@ -8,10 +9,11 @@ import ( const DATE_FORMAT = "2006/01/02 15:04:05" const LEASE_REGEX = `(?ms)lease\s+(?P[^\s]+)\s+{.*?starts\s\d+\s(?P[\d\/]+\s[\d\:]+);.*?ends\s\d+\s(?P[\d\/]+\s[\d\:]+);.*?hardware\sethernet\s(?P[\w\:]+);.*?}` -func Parse(contents string) (Leases, error) { +func parse(contents string) (Leases, error) { leases := Leases{} var re = regexp.MustCompile(LEASE_REGEX) matches := re.FindAllStringSubmatch(contents, -1) + var errs []error for _, m := range matches { rm := make(map[string]string) for i, name := range re.SubexpNames() { @@ -21,11 +23,11 @@ func Parse(contents string) (Leases, error) { } begin, err := time.Parse(DATE_FORMAT, rm["begin"]) if err != nil { - panic(err) + errs = append(errs, err) } end, err := time.Parse(DATE_FORMAT, rm["end"]) if err != nil { - panic(err) + errs = append(errs, err) } l := Lease{ @@ -36,5 +38,8 @@ func Parse(contents string) (Leases, error) { } leases = append(leases, l) } + if len(errs) > 0 { + return leases, errors.Join(errs...) + } return leases, nil } diff --git a/internal/leases/parser_test.go b/internal/leases/parser_test.go index 1480fe4..b2aeac1 100644 --- a/internal/leases/parser_test.go +++ b/internal/leases/parser_test.go @@ -34,7 +34,7 @@ lease 192.168.2.30 { func TestParse(t *testing.T) { assert := assert.New(t) - l, err := Parse(LEASES_CONTENT) + l, err := parse(LEASES_CONTENT) assert.NoError(err) b, _ := time.Parse(DATE_FORMAT, "2019/06/27 13:30:21") diff --git a/internal/leases/types.go b/internal/leases/types.go index 06177d5..de94934 100644 --- a/internal/leases/types.go +++ b/internal/leases/types.go @@ -34,12 +34,3 @@ func NewReportItem(l Lease, log *zap.SugaredLogger) *ReportItem { Log: log, } } - -func (i *ReportItem) MacContainedIn(macs []string) bool { - for _, m := range macs { - if m == i.Mac { - return true - } - } - return false -} diff --git a/internal/reporter/reporter.go b/internal/reporter/reporter.go index 073e176..8123a8b 100644 --- a/internal/reporter/reporter.go +++ b/internal/reporter/reporter.go @@ -1,14 +1,16 @@ package reporter import ( + "net/netip" "os" "os/signal" + "slices" "sync" "syscall" "time" - "github.com/metal-stack/metal-bmc/domain" "github.com/metal-stack/metal-bmc/internal/leases" + "github.com/metal-stack/metal-bmc/pkg/config" metalgo "github.com/metal-stack/metal-go" "github.com/metal-stack/metal-go/api/client/machine" "github.com/metal-stack/metal-go/api/models" @@ -17,13 +19,13 @@ import ( // reporter reports information about bmc, bios and dhcp ip of bmc to metal-api type reporter struct { - cfg *domain.Config + cfg *config.Config log *zap.SugaredLogger client metalgo.Client } // New will create a reporter for MachineIpmiReports -func New(log *zap.SugaredLogger, cfg *domain.Config, client metalgo.Client) (*reporter, error) { +func New(log *zap.SugaredLogger, cfg *config.Config, client metalgo.Client) (*reporter, error) { return &reporter{ cfg: cfg, log: log, @@ -41,7 +43,11 @@ func (r reporter) Run() { case <-periodic.C: ls, err := leases.ReadLeases(r.cfg.LeaseFile) if err != nil { - r.log.Fatalw("could not parse leases file", "error", err) + r.log.Errorw("could not parse leases file", "error", err) + } + if len(ls) == 0 { + r.log.Errorw("empty leases returned, nothing to report") + continue } active := ls.FilterActive() byMac := active.LatestByMac() @@ -54,6 +60,14 @@ func (r reporter) Run() { wg.Add(len(byMac)) for _, l := range byMac { + if !r.isInAllowedCidr(l.Ip) { + continue + } + + if slices.Contains(r.cfg.IgnoreMacs, l.Mac) { + continue + } + item := leases.NewReportItem(l, r.log) go func() { item.EnrichWithBMCDetails(r.cfg.IpmiPort, r.cfg.IpmiUser, r.cfg.IpmiPassword) @@ -76,6 +90,25 @@ func (r reporter) Run() { } } +func (r reporter) isInAllowedCidr(ip string) bool { + parsedIP, err := netip.ParseAddr(ip) + if err != nil { + r.log.Errorw("given ip is not parsable", "ip", ip, "error", err) + return false + } + for _, cidr := range r.cfg.AllowedCidrs { + cidr := cidr + pfx, err := netip.ParsePrefix(cidr) + if err != nil { + return false + } + if pfx.Contains(parsedIP) { + return true + } + } + return false +} + // report will send all gathered information about machines to the metal-api func (r reporter) report(items []*leases.ReportItem) error { partitionID := r.cfg.PartitionID @@ -83,15 +116,8 @@ func (r reporter) report(items []*leases.ReportItem) error { for _, item := range items { item := item - mac := item.Mac - - if item.MacContainedIn(r.cfg.IgnoreMacs) { - continue - } - - ip := item.Ip if item.UUID == nil { - r.log.Errorw("could not determine uuid of device", "mac", mac, "ip", ip) + r.log.Errorw("could not determine uuid of device", "mac", item.Mac, "ip", item.Ip) continue } diff --git a/main.go b/main.go index 1a364bf..02d4046 100644 --- a/main.go +++ b/main.go @@ -3,8 +3,8 @@ package main import ( "fmt" - "github.com/metal-stack/metal-bmc/domain" "github.com/metal-stack/metal-bmc/internal/bmc" + "github.com/metal-stack/metal-bmc/pkg/config" metalgo "github.com/metal-stack/metal-go" "github.com/metal-stack/metal-bmc/internal/reporter" @@ -16,11 +16,15 @@ import ( ) func main() { - var cfg domain.Config + var cfg config.Config if err := envconfig.Process("METAL_BMC", &cfg); err != nil { panic(fmt.Errorf("bad configuration: %w", err)) } + if err := cfg.Validate(); err != nil { + panic(fmt.Errorf("bad configuration: %w", err)) + } + level, err := zap.ParseAtomicLevel(cfg.LogLevel) if err != nil { panic(fmt.Errorf("can't initialize zap logger: %w", err)) diff --git a/domain/config.go b/pkg/config/config.go similarity index 89% rename from domain/config.go rename to pkg/config/config.go index dec66fe..b160973 100644 --- a/domain/config.go +++ b/pkg/config/config.go @@ -1,6 +1,7 @@ -package domain +package config import ( + "net/netip" "net/url" "time" ) @@ -19,6 +20,7 @@ type Config struct { IpmiUser string `required:"false" default:"ADMIN" desc:"the ipmi user" split_words:"true"` IpmiPassword string `required:"false" default:"ADMIN" desc:"the ipmi password" split_words:"true"` IgnoreMacs []string `required:"false" desc:"mac addresses to ignore" split_words:"true"` + AllowedCidrs []string `required:"false" desc:"filters dhcp leases" split_words:"true"` // NSQ connection parameters MQAddress string `required:"false" default:"localhost:4161" desc:"set the MQ server address" envconfig:"mq_address"` @@ -34,3 +36,14 @@ type Config struct { ConsoleCertFile string `required:"false" default:"cert.pem" desc:"cert file" envconfig:"console_cert_file"` ConsoleKeyFile string `required:"false" default:"key.pem" desc:"key file" envconfig:"console_key_file"` } + +func (c *Config) Validate() error { + for _, cidr := range c.AllowedCidrs { + cidr := cidr + _, err := netip.ParsePrefix(cidr) + if err != nil { + return err + } + } + return nil +}