e0f54ef320
Add `tomswall agent`: it pulls this device's compiled config from tomswallapi, differentially applies it, and reports the applied generation. It caches the last known-good config and, when the control plane is unreachable, keeps applying that cache — it never fails closed. - internal/agent: rendered-config types, HTTP client (fetch + status report), on-disk cache, on-device DNS resolver for dns sets (honors the device's configured resolver, fail-safe on lookup failure), and the pull-apply-report loop behind a mockable Applier. - Translate the interface-agnostic, address-matched rendered model into native tomswall config using the "all:<cidr>" any-interface source/dest form, reusing the existing differential engine. Named-set members are inlined as concrete addresses (native nft set references are a tracked follow-up). - cmd/tomswall: wire the `agent` subcommand (flags + TOMSWALL_* env, --once). - Unit tests: translation, cache, and the don't-fail-closed fallback loop. - Add DESIGN.md documenting the control-plane architecture.
90 lines
2.8 KiB
Go
90 lines
2.8 KiB
Go
package config
|
|
|
|
import "fmt"
|
|
|
|
type TunnelType string
|
|
|
|
const (
|
|
TunnelIPSec TunnelType = "ipsec"
|
|
TunnelIPSecNAT TunnelType = "ipsecnat"
|
|
TunnelIPIP TunnelType = "ipip"
|
|
TunnelGRE TunnelType = "gre"
|
|
TunnelL2TP TunnelType = "l2tp"
|
|
TunnelPPTPClient TunnelType = "pptpclient"
|
|
TunnelPPTPServer TunnelType = "pptpserver"
|
|
TunnelOpenVPN TunnelType = "openvpn"
|
|
TunnelOpenVPNClient TunnelType = "openvpnclient"
|
|
TunnelOpenVPNServer TunnelType = "openvpnserver"
|
|
TunnelTinc TunnelType = "tinc"
|
|
Tunnel6to4 TunnelType = "6to4"
|
|
TunnelGeneric TunnelType = "generic"
|
|
)
|
|
|
|
// Tunnel defines VPN tunnel rules that allow encapsulated traffic to pass
|
|
// between the firewall and remote gateways. The actual traffic flowing
|
|
// through the tunnel is handled by normal zone/policy/rules.
|
|
type Tunnel struct {
|
|
// Tunnel type. For ipsec, append ":ah" to use Authentication Headers (default: no AH).
|
|
// For openvpn variants, append ":tcp" or ":udp" (default: udp).
|
|
// For generic, append ":protocol" or ":protocol:port".
|
|
Type string `yaml:"type"`
|
|
|
|
// Zone of the physical interface through which tunnel traffic passes.
|
|
Zone string `yaml:"zone"`
|
|
|
|
// Remote tunnel gateway address(es). Use 0.0.0.0/0 or ::/0 for road warriors.
|
|
Gateways []string `yaml:"gateways"`
|
|
|
|
// Zones that the remote gateway host belongs to (for IPSEC ISAKMP traffic).
|
|
GatewayZones []string `yaml:"gateway_zones,omitempty"`
|
|
|
|
// Port override for openvpn/generic types (default: type-specific).
|
|
Port int `yaml:"port,omitempty"`
|
|
|
|
Comment string `yaml:"comment,omitempty"`
|
|
}
|
|
|
|
var validTunnelTypes = map[TunnelType]bool{
|
|
TunnelIPSec: true, TunnelIPSecNAT: true,
|
|
TunnelIPIP: true, TunnelGRE: true, TunnelL2TP: true,
|
|
TunnelPPTPClient: true, TunnelPPTPServer: true,
|
|
TunnelOpenVPN: true, TunnelOpenVPNClient: true, TunnelOpenVPNServer: true,
|
|
TunnelTinc: true, Tunnel6to4: true, TunnelGeneric: true,
|
|
}
|
|
|
|
func ParseTunnelType(s string) (TunnelType, string, bool) {
|
|
for i, c := range s {
|
|
if c == ':' {
|
|
return TunnelType(s[:i]), s[i+1:], true
|
|
}
|
|
}
|
|
return TunnelType(s), "", false
|
|
}
|
|
|
|
func (c *Config) validateTunnels() error {
|
|
for i, t := range c.Tunnels {
|
|
baseType, _, _ := ParseTunnelType(t.Type)
|
|
if !validTunnelTypes[baseType] {
|
|
return fmt.Errorf("tunnels[%d]: unknown tunnel type %q", i, baseType)
|
|
}
|
|
|
|
if t.Zone == "" {
|
|
return fmt.Errorf("tunnels[%d]: zone required", i)
|
|
}
|
|
if _, ok := c.Zones[t.Zone]; !ok {
|
|
return fmt.Errorf("tunnels[%d]: zone %q not defined", i, t.Zone)
|
|
}
|
|
|
|
if len(t.Gateways) == 0 {
|
|
return fmt.Errorf("tunnels[%d]: at least one gateway required", i)
|
|
}
|
|
|
|
for _, gz := range t.GatewayZones {
|
|
if _, ok := c.Zones[gz]; !ok {
|
|
return fmt.Errorf("tunnels[%d]: gateway zone %q not defined", i, gz)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|