From bf950f10ac23c780c0e6a332beb27f60ba51144f Mon Sep 17 00:00:00 2001 From: core Date: Mon, 2 Oct 2023 13:44:47 -0400 Subject: [PATCH] zone parsing & internal representation, new format for pancheri-render to render to internal rep & functional rendering cli --- cmd/pancheri-render/main.go | 60 +++++---------- cmd/pancheri/main.go | 11 +++ config.go | 3 + rule.go | 24 +++--- zone.go | 14 ++-- zone_config.go | 149 ++++++++++++++++++++++++++++++++++++ 6 files changed, 203 insertions(+), 58 deletions(-) create mode 100644 zone_config.go diff --git a/cmd/pancheri-render/main.go b/cmd/pancheri-render/main.go index fe96161..06acb94 100644 --- a/cmd/pancheri-render/main.go +++ b/cmd/pancheri-render/main.go @@ -2,14 +2,16 @@ package main import ( "flag" + "fmt" "git.e3t.cc/e3team/pancheri" - "net" + "gopkg.in/yaml.v2" "os" ) func main() { - //configPath := flag.String("zone", "", "Zone file to render") + zonePath := flag.String("zone", "", "Zone file to render") printUsage := flag.Bool("help", false, "Print command line usage") + renderFormat := flag.String("format", "bind", "Format to render to (bind/pancheri)") flag.Parse() @@ -18,42 +20,22 @@ func main() { os.Exit(0) } - var aRecords []pancheri.RecordA - aRecords = append(aRecords, pancheri.RecordA{ - In: "test.xe.", - Ip: net.ParseIP("1.2.3.4"), - TTL: 600, - }) - - var aaaaRecords []pancheri.RecordAAAA - aaaaRecords = append(aaaaRecords, pancheri.RecordAAAA{ - In: "testv6.xe.", - Ip: net.ParseIP("bd1f:f314:5398:0e3d:b3e0:f427:73ef:60fb"), - TTL: 600, - }) - - var cnameRecords []pancheri.RecordCNAME - cnameRecords = append(cnameRecords, pancheri.RecordCNAME{ - In: "cname.xe.", - Target: "test.xe.", - TTL: 600, - }) - - var txtRecords []pancheri.RecordTXT - txtRecords = append(txtRecords, pancheri.RecordTXT{ - In: "txt.xe.", - Content: []string{"WHY HELLO THERE MY FELLOW E3TEAMERS"}, - TTL: 600, - }) - - zone := pancheri.Zone{ - ReducedHash: "0433da05bf22d86c1886fca6e3e2c3239b86f1e6ebea9b94201483c8596c0468", - Root: "xe", - ARecords: aRecords, - AAAARecords: aaaaRecords, - CNAMERecords: cnameRecords, - TXTRecords: txtRecords, - Zonefile: "zone_example.yml", + zone, err := pancheri.LoadZone(*zonePath) + if err != nil { + fmt.Printf("error loading zone: %s\n", err) + os.Exit(1) + } + + if *renderFormat == "pancheri" { + msh, err := yaml.Marshal(zone) + if err != nil { + fmt.Printf("error saving zone: %s\n", err) + os.Exit(1) + } + fmt.Println(string(msh[:])) + } else if *renderFormat == "bind" { + println(zone.RenderZone()) + } else { + fmt.Printf("unrecognized render format: %s", *renderFormat) } - println(zone.RenderZone()) } diff --git a/cmd/pancheri/main.go b/cmd/pancheri/main.go index 63266ff..e88296b 100644 --- a/cmd/pancheri/main.go +++ b/cmd/pancheri/main.go @@ -31,6 +31,17 @@ func main() { } logrus.SetLevel(c.Logging.Level) + for _, zonefile := range c.Zone.LoadFiles { + logrus.WithFields(logrus.Fields{ + "file": zonefile, + }).Info("loading zone") + _, err := pancheri.LoadZone(zonefile) + if err != nil { + logrus.Errorf("failed to load zone %s: %s", zonefile, err) + os.Exit(1) + } + } + logrus.WithFields(logrus.Fields{ "host": c.Server.Host, "port": c.Server.Port, diff --git a/config.go b/config.go index 7f4df41..b268a73 100644 --- a/config.go +++ b/config.go @@ -19,6 +19,9 @@ type Config struct { Format string `yaml:"format"` Level logrus.Level `yaml:"level"` } + Zone struct { + LoadFiles []string `yaml:"load_files"` + } `yaml:"zone"` } func LoadConfig(path string) (*Config, error) { diff --git a/rule.go b/rule.go index 8f1aeea..2918267 100644 --- a/rule.go +++ b/rule.go @@ -13,9 +13,9 @@ const ( ) type RecordA struct { - In string - Ip net.IP - TTL uint + In string `yaml:"i"` + Ip net.IP `yaml:"v4"` + TTL uint `yaml:"t"` } func (record *RecordA) Render() *dns.A { @@ -31,9 +31,9 @@ func (record *RecordA) Render() *dns.A { } type RecordAAAA struct { - In string - Ip net.IP - TTL uint + In string `yaml:"i"` + Ip net.IP `yaml:"v6"` + TTL uint `yaml:"t"` } func (record *RecordAAAA) Render() *dns.AAAA { @@ -49,9 +49,9 @@ func (record *RecordAAAA) Render() *dns.AAAA { } type RecordCNAME struct { - In string - Target string - TTL uint + In string `yaml:"i"` + Target string `yaml:"tr"` + TTL uint `yaml:"t"` } func (record *RecordCNAME) Render() *dns.CNAME { @@ -67,9 +67,9 @@ func (record *RecordCNAME) Render() *dns.CNAME { } type RecordTXT struct { - In string - Content []string - TTL uint + In string `yaml:"i"` + Content []string `yaml:"c"` + TTL uint `yaml:"t"` } func (record *RecordTXT) Render() *dns.TXT { diff --git a/zone.go b/zone.go index 55341ab..d73bbc2 100644 --- a/zone.go +++ b/zone.go @@ -6,13 +6,13 @@ import ( ) type Zone struct { - Root string - ReducedHash string - Zonefile string - ARecords []RecordA - AAAARecords []RecordAAAA - CNAMERecords []RecordCNAME - TXTRecords []RecordTXT + Root string `yaml:"root"` + ReducedHash string `yaml:"rsha"` + Zonefile string `yaml:"zf"` + ARecords []RecordA `yaml:"ra"` + AAAARecords []RecordAAAA `yaml:"rav6"` + CNAMERecords []RecordCNAME `yaml:"rcn"` + TXTRecords []RecordTXT `yaml:"rtx"` } func (z *Zone) RenderZone() string { diff --git a/zone_config.go b/zone_config.go new file mode 100644 index 0000000..79226d2 --- /dev/null +++ b/zone_config.go @@ -0,0 +1,149 @@ +package pancheri + +import ( + "crypto/sha256" + "errors" + "fmt" + "github.com/sirupsen/logrus" + "gopkg.in/yaml.v2" + "net" + "os" +) + +type ZoneConfig struct { + Zone struct { + Root string `yaml:"root"` + Records []CfgRecord `yaml:"records"` + } `yaml:"zone"` +} + +type CfgRecord struct { + RecordType string `yaml:"type"` + Domains []string `yaml:"domains"` + Ipv4 net.IP `yaml:"ipv4"` + Ipv6 net.IP `yaml:"ipv6"` + Target string `yaml:"target"` + Content []string `yaml:"content"` + TTL uint `yaml:"ttl"` +} + +func LoadZone(path string) (*Zone, error) { + f, err := os.Open(path) + if err != nil { + return nil, err + } + + var cfg ZoneConfig + decoder := yaml.NewDecoder(f) + + err = decoder.Decode(&cfg) + if err != nil { + return nil, err + } + + err = f.Close() + if err != nil { + return nil, err + } + + reduced, err := yaml.Marshal(cfg) + if err != nil { + return nil, err + } + // ReducedHash is a hash of the zonefile after it has been re-marshaled by libyaml. + // This is intended to prevent pointless changes of the serial when only minor formatting changes have been made + // ex. adding an extra space somewhere won't trigger a new serial + reducedHash := fmt.Sprintf("%x", sha256.Sum256(reduced)) + // The serial is the first few bytes of this converted to an integer + + // validate and convert + zone := Zone{ + Root: cfg.Zone.Root, + ReducedHash: reducedHash, + Zonefile: path, + ARecords: nil, + AAAARecords: nil, + CNAMERecords: nil, + TXTRecords: nil, + } + + for _, record := range cfg.Zone.Records { + if record.RecordType == RuleTypeA { + // req.d fields: in, ip, ttl + if len(record.Domains) == 0 { + return nil, errors.New("A record must contain at least one domain") + } + if record.Ipv4 == nil { + return nil, errors.New("A record must contain ipv4 address") + } + if record.TTL == 0 { + return nil, errors.New("A record TTL cannot be 0 or empty") + } + for _, domain := range record.Domains { + zone.ARecords = append(zone.ARecords, RecordA{ + In: domain, + Ip: record.Ipv4.To4(), + TTL: record.TTL, + }) + } + } else if record.RecordType == RuleTypeAAAA { + // req.d fields: in, ip, ttl + if len(record.Domains) == 0 { + return nil, errors.New("AAAA record must contain at least one domain") + } + if record.Ipv6 == nil { + return nil, errors.New("AAAA record must contain ipv6 address") + } + if record.TTL == 0 { + return nil, errors.New("AAAA record TTL cannot be 0 or empty") + } + for _, domain := range record.Domains { + zone.AAAARecords = append(zone.AAAARecords, RecordAAAA{ + In: domain, + Ip: record.Ipv6, + TTL: record.TTL, + }) + } + } else if record.RecordType == RuleTypeCNAME { + // req.d fields: in, ip, ttl + if len(record.Domains) == 0 { + return nil, errors.New("CNAME record must contain at least one domain") + } + if len(record.Target) == 0 { + return nil, errors.New("CNAME record must contain target") + } + if record.TTL == 0 { + return nil, errors.New("CNAME record TTL cannot be 0 or empty") + } + for _, domain := range record.Domains { + zone.CNAMERecords = append(zone.CNAMERecords, RecordCNAME{ + In: domain, + Target: record.Target, + TTL: record.TTL, + }) + } + } else if record.RecordType == RuleTypeTXT { + // req.d fields: in, content, ttl + if len(record.Domains) == 0 { + return nil, errors.New("TXT record must contain at least one domain") + } + if len(record.Content) == 0 { + return nil, errors.New("TXT record must contain content") + } + if record.TTL == 0 { + return nil, errors.New("TXT record TTL cannot be 0 or empty") + } + for _, domain := range record.Domains { + zone.TXTRecords = append(zone.TXTRecords, RecordTXT{ + In: domain, + Content: record.Content, + TTL: record.TTL, + }) + } + } + } + + logrus.Debugf("%+v", zone) + + return &zone, nil +}