Merge pull request 'Agent: translate the NAT tier into native config' (#4) from benvin/agent-nat into main
Reviewed-on: #4
This commit was merged in pull request #4.
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) {
|
func TestTranslateRejectsUnknownAction(t *testing.T) {
|
||||||
rc := &RenderedConfig{Enforcing: true, Rules: []RenderedRule{{Action: "bogus"}}}
|
rc := &RenderedConfig{Enforcing: true, Rules: []RenderedRule{{Action: "bogus"}}}
|
||||||
if _, err := Translate(rc); err == nil {
|
if _, err := Translate(rc); err == nil {
|
||||||
|
|||||||
@@ -18,6 +18,36 @@ type RenderedConfig struct {
|
|||||||
Sets []RenderedSet `yaml:"sets,omitempty" json:"sets,omitempty"`
|
Sets []RenderedSet `yaml:"sets,omitempty" json:"sets,omitempty"`
|
||||||
Rules []RenderedRule `yaml:"rules,omitempty" json:"rules,omitempty"`
|
Rules []RenderedRule `yaml:"rules,omitempty" json:"rules,omitempty"`
|
||||||
Policies []RenderedPolicy `yaml:"policies,omitempty" json:"policies,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 {
|
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
|
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).
|
// indexSets maps set name -> concrete member CIDRs (invalid members skipped).
|
||||||
func indexSets(sets []RenderedSet) map[string][]string {
|
func indexSets(sets []RenderedSet) map[string][]string {
|
||||||
m := make(map[string][]string, len(sets))
|
m := make(map[string][]string, len(sets))
|
||||||
|
|||||||
Reference in New Issue
Block a user