Agent: translate the NAT tier into native config
The agent now maps the rendered NAT sections into native tomswall config: - snat/masquerade -> config.SNAT, expanding a rendered rule's egress interface list and source CIDRs into one native rule per (egress, source) pair (a native SNAT rule takes a single dest interface); carries address/probability. - netmap -> config.Netmap (from_net/to_net -> net1/net2 on the resolved interface). - 1:1 nat -> config.StaticNAT. Unit-tested end to end from RenderedConfig to config.Config.
This commit is contained in:
@@ -84,6 +84,41 @@ func TestTranslateBareZone(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestTranslateNATTier(t *testing.T) {
|
||||
prob := 0.5
|
||||
rc := &RenderedConfig{
|
||||
Enforcing: true,
|
||||
SNAT: []RenderedSNAT{
|
||||
{Action: "masquerade", Source: []string{"10.1.0.0/24"}, Egress: []string{"eth0", "eth3"}},
|
||||
{Action: "snat", Source: []string{"10.2.0.0/24"}, Egress: []string{"eth0"}, Address: "203.0.113.1", Probability: &prob},
|
||||
},
|
||||
Netmap: []RenderedNetmap{{Type: "dnat", FromNet: "10.0.0.0/24", ToNet: "192.168.1.0/24", Interface: "eth0"}},
|
||||
NAT: []RenderedNAT{{External: "203.0.113.10", Internal: "10.1.0.10", Interface: "eth0"}},
|
||||
}
|
||||
cfg, err := Translate(rc)
|
||||
if err != nil {
|
||||
t.Fatalf("Translate: %v", err)
|
||||
}
|
||||
|
||||
// masquerade with two egress interfaces expands to two rules; snat adds one.
|
||||
if len(cfg.SNAT) != 3 {
|
||||
t.Fatalf("expected 3 SNAT rules, got %d: %+v", len(cfg.SNAT), cfg.SNAT)
|
||||
}
|
||||
if cfg.SNAT[0].Action != config.SNATMasquerade || cfg.SNAT[0].Source != "10.1.0.0/24" || cfg.SNAT[0].Dest != "eth0" {
|
||||
t.Errorf("unexpected masquerade rule: %+v", cfg.SNAT[0])
|
||||
}
|
||||
if cfg.SNAT[2].Address != "203.0.113.1" || cfg.SNAT[2].Probability != 0.5 {
|
||||
t.Errorf("snat address/probability not carried: %+v", cfg.SNAT[2])
|
||||
}
|
||||
|
||||
if len(cfg.Netmap) != 1 || cfg.Netmap[0].Net1 != "10.0.0.0/24" || cfg.Netmap[0].Net2 != "192.168.1.0/24" || cfg.Netmap[0].Interface != "eth0" {
|
||||
t.Errorf("netmap not translated: %+v", cfg.Netmap)
|
||||
}
|
||||
if len(cfg.StaticNAT) != 1 || cfg.StaticNAT[0].External != "203.0.113.10" || cfg.StaticNAT[0].Internal != "10.1.0.10" {
|
||||
t.Errorf("static nat not translated: %+v", cfg.StaticNAT)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTranslateRejectsUnknownAction(t *testing.T) {
|
||||
rc := &RenderedConfig{Enforcing: true, Rules: []RenderedRule{{Action: "bogus"}}}
|
||||
if _, err := Translate(rc); err == nil {
|
||||
|
||||
@@ -18,6 +18,36 @@ type RenderedConfig struct {
|
||||
Sets []RenderedSet `yaml:"sets,omitempty" json:"sets,omitempty"`
|
||||
Rules []RenderedRule `yaml:"rules,omitempty" json:"rules,omitempty"`
|
||||
Policies []RenderedPolicy `yaml:"policies,omitempty" json:"policies,omitempty"`
|
||||
SNAT []RenderedSNAT `yaml:"snat,omitempty" json:"snat,omitempty"`
|
||||
Netmap []RenderedNetmap `yaml:"netmap,omitempty" json:"netmap,omitempty"`
|
||||
NAT []RenderedNAT `yaml:"nat,omitempty" json:"nat,omitempty"`
|
||||
}
|
||||
|
||||
// RenderedSNAT is a resolved SNAT/masquerade rule (egress carries interface names).
|
||||
type RenderedSNAT struct {
|
||||
Action string `yaml:"action" json:"action"`
|
||||
Source []string `yaml:"source,omitempty" json:"source,omitempty"`
|
||||
Egress []string `yaml:"egress" json:"egress"`
|
||||
Address string `yaml:"address,omitempty" json:"address,omitempty"`
|
||||
Probability *float64 `yaml:"probability,omitempty" json:"probability,omitempty"`
|
||||
Comment string `yaml:"comment,omitempty" json:"comment,omitempty"`
|
||||
}
|
||||
|
||||
// RenderedNetmap is a resolved network-to-network mapping on one interface.
|
||||
type RenderedNetmap struct {
|
||||
Type string `yaml:"type" json:"type"`
|
||||
FromNet string `yaml:"from_net" json:"from_net"`
|
||||
ToNet string `yaml:"to_net" json:"to_net"`
|
||||
Interface string `yaml:"interface,omitempty" json:"interface,omitempty"`
|
||||
Comment string `yaml:"comment,omitempty" json:"comment,omitempty"`
|
||||
}
|
||||
|
||||
// RenderedNAT is a resolved one-to-one static NAT on one interface.
|
||||
type RenderedNAT struct {
|
||||
External string `yaml:"external" json:"external"`
|
||||
Internal string `yaml:"internal" json:"internal"`
|
||||
Interface string `yaml:"interface,omitempty" json:"interface,omitempty"`
|
||||
Comment string `yaml:"comment,omitempty" json:"comment,omitempty"`
|
||||
}
|
||||
|
||||
type RenderedSettings struct {
|
||||
|
||||
@@ -60,9 +60,57 @@ func Translate(rc *RenderedConfig) (*config.Config, error) {
|
||||
})
|
||||
}
|
||||
|
||||
for _, s := range rc.SNAT {
|
||||
cfg.SNAT = append(cfg.SNAT, translateSNAT(s)...)
|
||||
}
|
||||
for _, n := range rc.Netmap {
|
||||
cfg.Netmap = append(cfg.Netmap, config.Netmap{
|
||||
Type: config.NetmapType(n.Type),
|
||||
Net1: n.FromNet,
|
||||
Net2: n.ToNet,
|
||||
Interface: n.Interface,
|
||||
Comment: n.Comment,
|
||||
})
|
||||
}
|
||||
for _, n := range rc.NAT {
|
||||
cfg.StaticNAT = append(cfg.StaticNAT, config.StaticNAT{
|
||||
External: n.External,
|
||||
Internal: n.Internal,
|
||||
Interface: n.Interface,
|
||||
Comment: n.Comment,
|
||||
})
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
// translateSNAT expands a rendered SNAT (which carries a list of egress
|
||||
// interfaces and source CIDRs) into native tomswall SNAT rules — one per
|
||||
// (egress interface, source) pair, since a native rule takes a single Dest.
|
||||
func translateSNAT(s RenderedSNAT) []config.SNATRule {
|
||||
sources := s.Source
|
||||
if len(sources) == 0 {
|
||||
sources = []string{""}
|
||||
}
|
||||
var out []config.SNATRule
|
||||
for _, egress := range s.Egress {
|
||||
for _, src := range sources {
|
||||
r := config.SNATRule{
|
||||
Action: config.SNATAction(s.Action),
|
||||
Source: src,
|
||||
Dest: egress,
|
||||
Address: s.Address,
|
||||
Comment: s.Comment,
|
||||
}
|
||||
if s.Probability != nil {
|
||||
r.Probability = *s.Probability
|
||||
}
|
||||
out = append(out, r)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// indexSets maps set name -> concrete member CIDRs (invalid members skipped).
|
||||
func indexSets(sets []RenderedSet) map[string][]string {
|
||||
m := make(map[string][]string, len(sets))
|
||||
|
||||
Reference in New Issue
Block a user