isle/go/cmd/entrypoint/vpn_firewall.go

208 lines
4.9 KiB
Go
Raw Normal View History

package main
import (
"errors"
"fmt"
"isle/daemon/daecommon"
2024-12-10 12:52:57 +00:00
"strings"
)
const vpnFirewallConfigChangeStagerName = "vpn-firewall-config"
2024-12-10 12:52:57 +00:00
// vpnFirewallGetConfigWithStaged returns the network config along with any
// staged firewall configuration changes, if there are any.
func vpnFirewallGetConfig(ctx subCmdCtx) (daecommon.NetworkConfig, error) {
config, err := ctx.getDaemonRPC().GetConfig(ctx)
if err != nil {
return daecommon.NetworkConfig{}, err
}
var firewallConfig daecommon.ConfigFirewall
if ok, err := ctx.opts.changeStager.get(
&firewallConfig, vpnFirewallConfigChangeStagerName,
); err != nil {
return daecommon.NetworkConfig{}, fmt.Errorf(
"getting staged VPN firewall config: %w", err,
)
} else if ok {
config.VPN.Firewall = firewallConfig
}
return config, nil
}
func vpnFirewallRuleSetToFn(
str string,
) (
func(*daecommon.ConfigFirewall) *[]daecommon.ConfigFirewallRule,
error,
) {
switch strings.ToLower(str) {
case "outbound":
return func(c *daecommon.ConfigFirewall) *[]daecommon.ConfigFirewallRule {
return &c.Outbound
}, nil
case "inbound":
return func(c *daecommon.ConfigFirewall) *[]daecommon.ConfigFirewallRule {
return &c.Inbound
}, nil
default:
return nil, fmt.Errorf("must be 'inbound' or 'outbound'")
}
}
var subCmdVPNFirewallAdd = subCmd{
name: "add",
descr: "Add a new firewall rule to the staged configuration",
do: func(ctx subCmdCtx) error {
to := ctx.flags.String(
"to",
"",
"Which set of rules to add to, either 'inbound' or 'outbound'",
)
var rule daecommon.ConfigFirewallRule
ctx.flags.StringVar(
&rule.Port, "port", "any", "Port number or range to allow",
)
ctx.flags.StringVar(
&rule.Proto,
"proto",
"any",
"Protocol to allow. Can be 'tcp', 'udp', 'icmp', or 'any'",
)
ctx.flags.StringVar(
&rule.Host,
"host",
"",
"Name of host to allow. Defaults to 'any' if --groups is not given",
)
ctx.flags.StringSliceVar(
&rule.Groups,
"groups",
nil,
"One or more comma-separated group names to allow",
)
ctx, err := ctx.withParsedFlags()
if err != nil {
return fmt.Errorf("parsing flags: %w", err)
}
if *to == "" {
return errors.New("--to is required")
}
ruleSetFn, err := vpnFirewallRuleSetToFn(*to)
if err != nil {
return fmt.Errorf("invalid --to value %q: %w", *to, err)
}
if rule.Host != "" && len(rule.Groups) > 0 {
return fmt.Errorf("--host and --groups are mutually exclusive")
} else if rule.Host == "" && len(rule.Groups) == 0 {
rule.Host = "any"
}
config, err := vpnFirewallGetConfig(ctx)
if err != nil {
return fmt.Errorf("getting network config: %w", err)
}
ruleSet := ruleSetFn(&config.VPN.Firewall)
*ruleSet = append(*ruleSet, rule)
if err := config.Validate(); err != nil {
return err
}
if err := ctx.opts.changeStager.set(
config.VPN.Firewall, vpnFirewallConfigChangeStagerName,
); err != nil {
return fmt.Errorf("staging changes: %w", err)
}
return nil
},
}
type firewallRuleView struct {
Index int `yaml:"index"`
daecommon.ConfigFirewallRule `yaml:",inline"`
}
func newFirewallRuleViews(
rules []daecommon.ConfigFirewallRule,
) []firewallRuleView {
views := make([]firewallRuleView, len(rules))
for i := range rules {
views[i] = firewallRuleView{
Index: i,
ConfigFirewallRule: rules[i],
}
}
return views
}
type firewallView struct {
Outbound []firewallRuleView `yaml:"outbound"`
Inbound []firewallRuleView `yaml:"inbound"`
}
func newFirewallView(firewallConfig daecommon.ConfigFirewall) firewallView {
return firewallView{
Outbound: newFirewallRuleViews(firewallConfig.Outbound),
Inbound: newFirewallRuleViews(firewallConfig.Inbound),
}
}
var subCmdVPNFirewallList = subCmd{
name: "list",
descr: "List all currently configured firewall rules",
do: doWithOutput(func(ctx subCmdCtx) (any, error) {
staged := ctx.flags.Bool(
"staged",
false,
"Return the firewall configuration with staged changes included",
)
ctx, err := ctx.withParsedFlags()
if err != nil {
return nil, fmt.Errorf("parsing flags: %w", err)
}
var firewallConfig daecommon.ConfigFirewall
if !*staged {
config, err := ctx.getDaemonRPC().GetConfig(ctx)
if err != nil {
return nil, fmt.Errorf("getting network config: %w", err)
}
firewallConfig = config.VPN.Firewall
} else if ok, err := ctx.opts.changeStager.get(
&firewallConfig, vpnFirewallConfigChangeStagerName,
); err != nil {
return nil, fmt.Errorf("checking for staged changes: %w", err)
} else if !ok {
return nil, errors.New("no firewall configuration changes have been staged")
}
return newFirewallView(firewallConfig), nil
}),
}
var subCmdVPNFirewall = subCmd{
name: "firewall",
descr: "Sub-commands related to this host's VPN firewall",
do: func(ctx subCmdCtx) error {
return ctx.doSubCmd(
2024-12-10 12:52:57 +00:00
subCmdVPNFirewallAdd,
subCmdVPNFirewallList,
)
},
}