8d9a76c751
Rewrites the compiler from ~440 to ~1700 lines covering all major shorewall firewall features: loopback, conntrack fast-path, anti-spoof, DHCP, intra-zone, blacklist/whitelist, conntrack notrack, tunnels (13 types), rules with sections, DNAT/redirect, SNAT/masquerade, static NAT, policies with zone exclusions, MSS clamping, rate limiting, connection limiting, negated addresses, ICMP type matching, TCP RST reject, user/UID matching, mark match/set, NFQUEUE, NONAT, and policy-level rate/conn limiting. Adds full config types for all shorewall subsystems (mangle, accounting, maclist, netmap, providers, tunnels, conntrack, blrules, proxyarp/ndp, routes, tc, secmarks), shorewall migration tooling, expanded CLI commands, expression-level diff engine, and 49 unit tests.
89 lines
2.0 KiB
Go
89 lines
2.0 KiB
Go
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
|
|
}
|