package config import ( "fmt" "unicode" ) type ZoneType string const ( ZoneIP ZoneType = "ip" ZoneIPSec ZoneType = "ipsec" ZoneFirewall ZoneType = "firewall" ZoneBPort ZoneType = "bport" ZoneLoopback ZoneType = "loopback" ZoneLocal ZoneType = "local" ) type Zone struct { Type ZoneType `yaml:"type"` Parents []string `yaml:"parents,omitempty"` Options []string `yaml:"options,omitempty"` InOptions []string `yaml:"in_options,omitempty"` OutOptions []string `yaml:"out_options,omitempty"` } var reservedZoneNames = map[string]bool{ "all": true, "none": true, "any": true, "SOURCE": true, "DEST": true, } func (c *Config) validateZones() error { if len(c.Zones) == 0 { return fmt.Errorf("no zones defined") } firewallCount := 0 for name, z := range c.Zones { if err := validateZoneName(name); err != nil { return fmt.Errorf("zone %q: %w", name, err) } switch z.Type { case ZoneIP, ZoneIPSec, ZoneFirewall, ZoneBPort, ZoneLoopback, ZoneLocal: case "": return fmt.Errorf("zone %q: type required", name) default: return fmt.Errorf("zone %q: unknown type %q", name, z.Type) } if z.Type == ZoneFirewall { firewallCount++ if len(z.Options) > 0 || len(z.InOptions) > 0 || len(z.OutOptions) > 0 { return fmt.Errorf("zone %q: firewall zone does not accept options", name) } } for _, parent := range z.Parents { if _, ok := c.Zones[parent]; !ok { return fmt.Errorf("zone %q: parent zone %q not defined", name, parent) } } } if firewallCount != 1 { return fmt.Errorf("exactly one firewall zone required, found %d", firewallCount) } return nil } func validateZoneName(name string) error { if reservedZoneNames[name] { return fmt.Errorf("reserved name") } if len(name) == 0 { return fmt.Errorf("empty name") } if !unicode.IsLetter(rune(name[0])) { return fmt.Errorf("must start with a letter") } for _, r := range name { if !unicode.IsLetter(r) && !unicode.IsDigit(r) && r != '_' { return fmt.Errorf("invalid character %q", r) } } return nil }