// Package model holds Housecarl's data: the firewall configuration // (State), users and tokens, the history of changes and the audit log. package model import ( "encoding/json" "sort" "strings" ) // State is the whole firewall configuration. It is small and changed as a // whole (internal/engine); the ruleset is rendered from it // (internal/render). type State struct { Settings Settings `json:"settings"` Zones []*Zone `json:"zones"` Sets []*AddressSet `json:"sets"` Services []*Service `json:"services"` Rules []*Rule `json:"rules"` Forwards []*PortForward `json:"forwards"` SNATs []*SourceNAT `json:"snats"` Redirects []*Redirect `json:"redirects"` BanLists []*BanList `json:"ban_lists"` } // Object kinds, for changes and the history. const ( KindSettings = "settings" KindZone = "zone" KindSet = "set" KindService = "service" KindRule = "rule" KindForward = "forward" KindSNAT = "snat" KindRedirect = "redirect" KindBanList = "banlist" ) // Kinds lists the object kinds (not settings) in display order. var Kinds = []string{KindZone, KindSet, KindService, KindRule, KindForward, KindSNAT, KindRedirect, KindBanList} // Object is anything in the State with an ID. type Object interface { GetID() int64 Label() string } // NewState is an empty configuration with the default settings. func NewState() *State { st := &State{Settings: DefaultSettings()} st.Fill() return st } // Fill replaces nil lists with empty ones (for JSON and comparisons). func (st *State) Fill() { if st.Zones == nil { st.Zones = []*Zone{} } if st.Sets == nil { st.Sets = []*AddressSet{} } if st.Services == nil { st.Services = []*Service{} } if st.Rules == nil { st.Rules = []*Rule{} } if st.Forwards == nil { st.Forwards = []*PortForward{} } if st.SNATs == nil { st.SNATs = []*SourceNAT{} } if st.Redirects == nil { st.Redirects = []*Redirect{} } if st.BanLists == nil { st.BanLists = []*BanList{} } } // Objects returns the objects of a kind. func (st *State) Objects(kind string) []Object { var out []Object switch kind { case KindZone: for _, o := range st.Zones { out = append(out, o) } case KindSet: for _, o := range st.Sets { out = append(out, o) } case KindService: for _, o := range st.Services { out = append(out, o) } case KindRule: for _, o := range st.Rules { out = append(out, o) } case KindForward: for _, o := range st.Forwards { out = append(out, o) } case KindSNAT: for _, o := range st.SNATs { out = append(out, o) } case KindRedirect: for _, o := range st.Redirects { out = append(out, o) } case KindBanList: for _, o := range st.BanLists { out = append(out, o) } } return out } // Put replaces (or adds, or with data nil deletes) the object of a kind // with an ID, from its JSON. // Empty reports whether nothing has been configured: no objects of any // kind. housecarld applies no ruleset for an empty configuration, so // installing and starting it doesn't close the machine. func (st *State) Empty() bool { for _, k := range Kinds { if len(st.Objects(k)) > 0 { return false } } return true } func (st *State) Put(kind string, id int64, data json.RawMessage) error { switch kind { case KindZone: return put(&st.Zones, id, data) case KindSet: return put(&st.Sets, id, data) case KindService: return put(&st.Services, id, data) case KindRule: return put(&st.Rules, id, data) case KindForward: return put(&st.Forwards, id, data) case KindSNAT: return put(&st.SNATs, id, data) case KindRedirect: return put(&st.Redirects, id, data) case KindBanList: return put(&st.BanLists, id, data) } return &unknownKind{kind} } type unknownKind struct{ kind string } func (e *unknownKind) Error() string { return "unknown kind " + e.kind } func put[T Object](list *[]T, id int64, data json.RawMessage) error { keep := (*list)[:0:0] for _, o := range *list { if o.GetID() != id { keep = append(keep, o) } } if data != nil { var o T if err := json.Unmarshal(data, &o); err != nil { return err } keep = append(keep, o) } *list = keep return nil } // Clone returns a deep copy. func (st *State) Clone() *State { b, err := json.Marshal(st) if err != nil { panic(err) } var out State if err := json.Unmarshal(b, &out); err != nil { panic(err) } out.Fill() return &out } // Sort puts the objects in display order: named objects by name, rules by // chain and position, NAT rules by position. func (st *State) Sort() { sort.SliceStable(st.Zones, func(i, j int) bool { return st.Zones[i].Name < st.Zones[j].Name }) sort.SliceStable(st.Sets, func(i, j int) bool { return st.Sets[i].Name < st.Sets[j].Name }) sort.SliceStable(st.Services, func(i, j int) bool { return st.Services[i].Name < st.Services[j].Name }) sort.SliceStable(st.BanLists, func(i, j int) bool { return st.BanLists[i].Name < st.BanLists[j].Name }) sort.SliceStable(st.Rules, func(i, j int) bool { a, b := st.Rules[i], st.Rules[j] if a.Chain != b.Chain { return ChainOrder(a.Chain) < ChainOrder(b.Chain) } return a.Position < b.Position }) sort.SliceStable(st.Forwards, func(i, j int) bool { return st.Forwards[i].Position < st.Forwards[j].Position }) sort.SliceStable(st.SNATs, func(i, j int) bool { return st.SNATs[i].Position < st.SNATs[j].Position }) sort.SliceStable(st.Redirects, func(i, j int) bool { return st.Redirects[i].Position < st.Redirects[j].Position }) } // Renumber gives the rules of each chain, and the NAT rules of each kind, // positions 1, 2, 3, … in their current order (after an insertion at a // fractional position, or a deletion). func (st *State) Renumber() { st.Sort() n := map[string]float64{} for _, r := range st.Rules { n[r.Chain]++ r.Position = n[r.Chain] } for i, f := range st.Forwards { f.Position = float64(i + 1) } for i, s := range st.SNATs { s.Position = float64(i + 1) } for i, r := range st.Redirects { r.Position = float64(i + 1) } } // Zone finds a zone by name. func (st *State) Zone(name string) *Zone { for _, z := range st.Zones { if strings.EqualFold(z.Name, name) { return z } } return nil } // Set finds an address set by name. func (st *State) Set(name string) *AddressSet { for _, s := range st.Sets { if strings.EqualFold(s.Name, name) { return s } } return nil } // Service finds a service by name: the configuration's, else the built-in // catalog's. func (st *State) Service(name string) *Service { for _, s := range st.Services { if strings.EqualFold(s.Name, name) { return s } } return BuiltinService(name) } // ByID finds an object of a kind by ID (nil if there is none). func (st *State) ByID(kind string, id int64) Object { for _, o := range st.Objects(kind) { if o.GetID() == id { return o } } return nil } // Find finds an object of a kind by ID or name (zones, sets, services, // port forwards, ban lists) or by ID alone (rules, source NAT, redirects). func (st *State) Find(kind, ref string) Object { for _, o := range st.Objects(kind) { if itoa(o.GetID()) == ref { return o } } switch kind { case KindZone: if z := st.Zone(ref); z != nil { return z } case KindSet: if s := st.Set(ref); s != nil { return s } case KindService: for _, s := range st.Services { if strings.EqualFold(s.Name, ref) { return s } } case KindForward: for _, f := range st.Forwards { if f.Name != "" && strings.EqualFold(f.Name, ref) { return f } } case KindBanList: if b := st.BanList(ref); b != nil { return b } } return nil } // BanList finds a ban list by name. func (st *State) BanList(name string) *BanList { for _, b := range st.BanLists { if strings.EqualFold(b.Name, name) { return b } } return nil } func itoa(n int64) string { b, _ := json.Marshal(n) return string(b) } // ExternalInterfaces lists the interfaces of the external zones. func (st *State) ExternalInterfaces() []string { return st.zoneInterfaces(func(z *Zone) bool { return z.External }) } // InternalInterfaces lists the interfaces of the internal zones. func (st *State) InternalInterfaces() []string { return st.zoneInterfaces(func(z *Zone) bool { return !z.External }) } func (st *State) zoneInterfaces(keep func(*Zone) bool) []string { seen := map[string]bool{} var out []string for _, z := range st.Zones { if !keep(z) { continue } for _, i := range z.Interfaces { if !seen[i] { seen[i] = true out = append(out, i) } } } sort.Strings(out) return out }