package model import "fmt" // Chains rules are placed in. const ( // ChainEarly sees packets before connection tracking (prerouting, // priority raw): cheap drops, antispoofing. ChainEarly = "early" // ChainInput is traffic to the firewall itself. ChainInput = "input" // ChainForward is traffic through the firewall. ChainForward = "forward" // ChainOutput is traffic from the firewall itself. ChainOutput = "output" ) // Chains lists the rule chains in display order. var Chains = []string{ChainEarly, ChainInput, ChainForward, ChainOutput} // ChainOrder is a chain's place in Chains. func ChainOrder(c string) int { for i, x := range Chains { if x == c { return i } } return len(Chains) } // Rule actions. const ( ActionAccept = "accept" ActionDrop = "drop" ActionReject = "reject" // ActionLog only logs (and counts); the packet goes on to the next // rule. ActionLog = "log" ) // Actions lists the rule actions. var Actions = []string{ActionAccept, ActionDrop, ActionReject, ActionLog} // Families a rule can be limited to; "" is both. const ( FamilyIPv4 = "ipv4" FamilyIPv6 = "ipv6" ) // Zone is a group of interfaces (wildcards such as "incus*" allowed). // External zones face the internet: built-ins such as antispoofing and // MSS clamping apply to them. type Zone struct { ID int64 `json:"id"` Name string `json:"name"` Interfaces []string `json:"interfaces"` External bool `json:"external,omitempty"` Comment string `json:"comment,omitempty"` } func (z *Zone) GetID() int64 { return z.ID } func (z *Zone) Label() string { return z.Name } // AddressSet is a named list of IPv4 and IPv6 addresses, prefixes and // ranges. A dynamic set also takes entries at run time, each with a // timeout, through its own API (a captive portal's signed-in clients). type AddressSet struct { ID int64 `json:"id"` Name string `json:"name"` Entries []string `json:"entries"` Dynamic bool `json:"dynamic,omitempty"` // Timeout is a dynamic set's default entry lifetime in seconds (0: // entries stay until removed). Timeout int `json:"timeout,omitempty"` Comment string `json:"comment,omitempty"` } func (s *AddressSet) GetID() int64 { return s.ID } func (s *AddressSet) Label() string { return s.Name } // Service is a named list of ports ("tcp/22", "udp/60000-61000") and ICMP // types ("icmp/echo-request", "icmpv6/packet-too-big"). Rules name // services, or give such entries directly. type Service struct { ID int64 `json:"id"` Name string `json:"name"` Entries []string `json:"entries"` Comment string `json:"comment,omitempty"` // Builtin marks the catalog's services (never stored). Builtin bool `json:"builtin,omitempty"` } func (s *Service) GetID() int64 { return s.ID } func (s *Service) Label() string { return s.Name } // Match is what a rule, port forward, source NAT or redirect looks at. // Lists are ORed; the fields together are ANDed. type Match struct { // InZones and OutZones are zone names; with NotIn/NotOut, every zone // but those. InZones []string `json:"in_zones,omitempty"` NotIn bool `json:"not_in,omitempty"` OutZones []string `json:"out_zones,omitempty"` NotOut bool `json:"not_out,omitempty"` // Sources and Destinations are set names or literal addresses, // prefixes and ranges, IPv4 or IPv6. Sources []string `json:"sources,omitempty"` NotSources bool `json:"not_sources,omitempty"` Destinations []string `json:"destinations,omitempty"` NotDests bool `json:"not_destinations,omitempty"` // Services are service names or entries ("tcp/22", "icmp/echo-request"). Services []string `json:"services,omitempty"` // Family limits the rule to ipv4 or ipv6 ("": what its addresses // and services allow). Family string `json:"family,omitempty"` } // Rule is one line of a chain, checked in order. type Rule struct { ID int64 `json:"id"` Chain string `json:"chain"` Position float64 `json:"position"` Match // CtState: new, established, related, invalid, untracked. CtState []string `json:"ct_state,omitempty"` // CtStatus: dnat, snat (a connection that was NATed). CtStatus []string `json:"ct_status,omitempty"` // Days ("Monday", …) and hours ("08:00"-"17:30") the rule applies. Days []string `json:"days,omitempty"` HourFrom string `json:"hour_from,omitempty"` HourTo string `json:"hour_to,omitempty"` // Limit, if set, makes the rule match only packets within (or with // LimitOver, beyond) a rate. Limit *Limit `json:"limit,omitempty"` // ConnLimit matches when a source already has more than this many // connections (per source address). ConnLimit int `json:"conn_limit,omitempty"` Action string `json:"action"` // RejectWith: "" (ICMP port/admin unreachable as fits) or tcp-reset. RejectWith string `json:"reject_with,omitempty"` Log bool `json:"log,omitempty"` LogPrefix string `json:"log_prefix,omitempty"` // Raw, when set, is an nftables rule written by hand, used instead of // the fields above (the escape hatch for what Housecarl can't say). Raw string `json:"raw,omitempty"` Comment string `json:"comment,omitempty"` Disabled bool `json:"disabled,omitempty"` } func (r *Rule) GetID() int64 { return r.ID } func (r *Rule) Label() string { if r.Comment != "" { return fmt.Sprintf("%s #%d (%s)", r.Chain, r.ID, r.Comment) } return fmt.Sprintf("%s #%d", r.Chain, r.ID) } // Limit is a rate: Rate packets per Per (second, minute, hour, day), with // a burst. type Limit struct { Rate int `json:"rate"` Per string `json:"per"` Burst int `json:"burst,omitempty"` // Over matches packets beyond the rate instead of within it (to drop // floods). Over bool `json:"over,omitempty"` // PerSource counts each source address on its own (a meter), instead // of all traffic the rule sees together. PerSource bool `json:"per_source,omitempty"` } // PortForward publishes a service on an inside host: incoming connections // to a public address and port are sent on (DNAT) to To[:ToPort], let // through the forward chain, and, with Hairpin, also work from inside. // To6 also opens the port to an IPv6 host (a pinhole; no translation). type PortForward struct { ID int64 `json:"id"` Position float64 `json:"position"` Name string `json:"name,omitempty"` // Protocols: tcp, udp (default tcp). Protocols []string `json:"protocols"` // Ports is the public port or range ("8096", "7777-7780"). Ports string `json:"ports"` // PublicAddresses are the IPv4 addresses (or set names) to match; // none means any address on the external zones' interfaces (and then // no hairpin). PublicAddresses []string `json:"public_addresses,omitempty"` // Sources limits who may connect (set names or addresses). Sources []string `json:"sources,omitempty"` To string `json:"to,omitempty"` // ToPort is the inside port ("" keeps the public one; for a range, the // first port of an equally long range). ToPort string `json:"to_port,omitempty"` To6 string `json:"to6,omitempty"` // Hairpin lets inside clients use the public address: their // connections are masqueraded so replies come back through the // firewall. HairpinSources limits that to some sources (none: all // inside traffic). Hairpin bool `json:"hairpin,omitempty"` HairpinSources []string `json:"hairpin_sources,omitempty"` Comment string `json:"comment,omitempty"` Disabled bool `json:"disabled,omitempty"` } func (f *PortForward) GetID() int64 { return f.ID } func (f *PortForward) Label() string { if f.Name != "" { return f.Name } return fmt.Sprintf("port forward #%d (%s)", f.ID, f.Ports) } // SourceNAT rewrites the source address of outgoing traffic: masquerade // (To empty: the outgoing interface's address) or SNAT to To. type SourceNAT struct { ID int64 `json:"id"` Position float64 `json:"position"` Match To string `json:"to,omitempty"` Comment string `json:"comment,omitempty"` Disabled bool `json:"disabled,omitempty"` } func (s *SourceNAT) GetID() int64 { return s.ID } func (s *SourceNAT) Label() string { if s.Comment != "" { return fmt.Sprintf("source NAT #%d (%s)", s.ID, s.Comment) } return fmt.Sprintf("source NAT #%d", s.ID) } // Redirect sends matching traffic to a port on the firewall itself (to // make every client use its DNS server, say). type Redirect struct { ID int64 `json:"id"` Position float64 `json:"position"` Match // ToPort is the local port (the services' first port when empty). ToPort string `json:"to_port,omitempty"` Comment string `json:"comment,omitempty"` Disabled bool `json:"disabled,omitempty"` } func (r *Redirect) GetID() int64 { return r.ID } func (r *Redirect) Label() string { if r.Comment != "" { return fmt.Sprintf("redirect #%d (%s)", r.ID, r.Comment) } return fmt.Sprintf("redirect #%d", r.ID) }