zone parsing & internal representation, new format for pancheri-render to render to internal rep & functional rendering cli
This commit is contained in:
parent
1a84386394
commit
bf950f10ac
|
@ -2,14 +2,16 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
|
"fmt"
|
||||||
"git.e3t.cc/e3team/pancheri"
|
"git.e3t.cc/e3team/pancheri"
|
||||||
"net"
|
"gopkg.in/yaml.v2"
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
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")
|
printUsage := flag.Bool("help", false, "Print command line usage")
|
||||||
|
renderFormat := flag.String("format", "bind", "Format to render to (bind/pancheri)")
|
||||||
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
|
@ -18,42 +20,22 @@ func main() {
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
var aRecords []pancheri.RecordA
|
zone, err := pancheri.LoadZone(*zonePath)
|
||||||
aRecords = append(aRecords, pancheri.RecordA{
|
if err != nil {
|
||||||
In: "test.xe.",
|
fmt.Printf("error loading zone: %s\n", err)
|
||||||
Ip: net.ParseIP("1.2.3.4"),
|
os.Exit(1)
|
||||||
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",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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())
|
println(zone.RenderZone())
|
||||||
|
} else {
|
||||||
|
fmt.Printf("unrecognized render format: %s", *renderFormat)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,17 @@ func main() {
|
||||||
}
|
}
|
||||||
logrus.SetLevel(c.Logging.Level)
|
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{
|
logrus.WithFields(logrus.Fields{
|
||||||
"host": c.Server.Host,
|
"host": c.Server.Host,
|
||||||
"port": c.Server.Port,
|
"port": c.Server.Port,
|
||||||
|
|
|
@ -19,6 +19,9 @@ type Config struct {
|
||||||
Format string `yaml:"format"`
|
Format string `yaml:"format"`
|
||||||
Level logrus.Level `yaml:"level"`
|
Level logrus.Level `yaml:"level"`
|
||||||
}
|
}
|
||||||
|
Zone struct {
|
||||||
|
LoadFiles []string `yaml:"load_files"`
|
||||||
|
} `yaml:"zone"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadConfig(path string) (*Config, error) {
|
func LoadConfig(path string) (*Config, error) {
|
||||||
|
|
24
rule.go
24
rule.go
|
@ -13,9 +13,9 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
type RecordA struct {
|
type RecordA struct {
|
||||||
In string
|
In string `yaml:"i"`
|
||||||
Ip net.IP
|
Ip net.IP `yaml:"v4"`
|
||||||
TTL uint
|
TTL uint `yaml:"t"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (record *RecordA) Render() *dns.A {
|
func (record *RecordA) Render() *dns.A {
|
||||||
|
@ -31,9 +31,9 @@ func (record *RecordA) Render() *dns.A {
|
||||||
}
|
}
|
||||||
|
|
||||||
type RecordAAAA struct {
|
type RecordAAAA struct {
|
||||||
In string
|
In string `yaml:"i"`
|
||||||
Ip net.IP
|
Ip net.IP `yaml:"v6"`
|
||||||
TTL uint
|
TTL uint `yaml:"t"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (record *RecordAAAA) Render() *dns.AAAA {
|
func (record *RecordAAAA) Render() *dns.AAAA {
|
||||||
|
@ -49,9 +49,9 @@ func (record *RecordAAAA) Render() *dns.AAAA {
|
||||||
}
|
}
|
||||||
|
|
||||||
type RecordCNAME struct {
|
type RecordCNAME struct {
|
||||||
In string
|
In string `yaml:"i"`
|
||||||
Target string
|
Target string `yaml:"tr"`
|
||||||
TTL uint
|
TTL uint `yaml:"t"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (record *RecordCNAME) Render() *dns.CNAME {
|
func (record *RecordCNAME) Render() *dns.CNAME {
|
||||||
|
@ -67,9 +67,9 @@ func (record *RecordCNAME) Render() *dns.CNAME {
|
||||||
}
|
}
|
||||||
|
|
||||||
type RecordTXT struct {
|
type RecordTXT struct {
|
||||||
In string
|
In string `yaml:"i"`
|
||||||
Content []string
|
Content []string `yaml:"c"`
|
||||||
TTL uint
|
TTL uint `yaml:"t"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (record *RecordTXT) Render() *dns.TXT {
|
func (record *RecordTXT) Render() *dns.TXT {
|
||||||
|
|
14
zone.go
14
zone.go
|
@ -6,13 +6,13 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Zone struct {
|
type Zone struct {
|
||||||
Root string
|
Root string `yaml:"root"`
|
||||||
ReducedHash string
|
ReducedHash string `yaml:"rsha"`
|
||||||
Zonefile string
|
Zonefile string `yaml:"zf"`
|
||||||
ARecords []RecordA
|
ARecords []RecordA `yaml:"ra"`
|
||||||
AAAARecords []RecordAAAA
|
AAAARecords []RecordAAAA `yaml:"rav6"`
|
||||||
CNAMERecords []RecordCNAME
|
CNAMERecords []RecordCNAME `yaml:"rcn"`
|
||||||
TXTRecords []RecordTXT
|
TXTRecords []RecordTXT `yaml:"rtx"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (z *Zone) RenderZone() string {
|
func (z *Zone) RenderZone() string {
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
Loading…
Reference in New Issue