diff --git a/go/cmd/entrypoint/main.go b/go/cmd/entrypoint/main.go index dd2852a..1383c33 100644 --- a/go/cmd/entrypoint/main.go +++ b/go/cmd/entrypoint/main.go @@ -38,10 +38,10 @@ var rootCmd = subCmd{ subCmdDaemon, subCmdGarage, subCmdHost, - subCmdNebula, subCmdNetwork, subCmdStorage, subCmdVersion, + subCmdVPN, ) }, } diff --git a/go/cmd/entrypoint/storage.go b/go/cmd/entrypoint/storage.go index 1d9cb88..ac7a787 100644 --- a/go/cmd/entrypoint/storage.go +++ b/go/cmd/entrypoint/storage.go @@ -197,7 +197,7 @@ var subCmdStorageAllocationRemove = subCmd{ var subCmdStorage = subCmd{ name: "storage", - descr: "Sub-commands having to do with configuration of storage on this host", + descr: "Sub-commands related to configuration of storage on this host", do: func(ctx subCmdCtx) error { return ctx.doSubCmd( subCmdStorageAllocationAdd, diff --git a/go/cmd/entrypoint/nebula.go b/go/cmd/entrypoint/vpn.go similarity index 87% rename from go/cmd/entrypoint/nebula.go rename to go/cmd/entrypoint/vpn.go index 9354ca1..be24b05 100644 --- a/go/cmd/entrypoint/nebula.go +++ b/go/cmd/entrypoint/vpn.go @@ -7,7 +7,7 @@ import ( "os" ) -var subCmdNebulaCreateCert = subCmd{ +var subCmdVPNCreateCert = subCmd{ name: "create-cert", descr: "Creates a signed nebula certificate file for an existing host and writes it to stdout", do: func(ctx subCmdCtx) error { @@ -63,12 +63,13 @@ var subCmdNebulaCreateCert = subCmd{ }, } -var subCmdNebula = subCmd{ - name: "nebula", - descr: "Sub-commands related to the nebula VPN", +var subCmdVPN = subCmd{ + name: "vpn", + descr: "Sub-commands related to this host's VPN, which connects it to other hosts in the network", do: func(ctx subCmdCtx) error { return ctx.doSubCmd( - subCmdNebulaCreateCert, + subCmdVPNCreateCert, + subCmdVPNFirewall, ) }, } diff --git a/go/cmd/entrypoint/vpn_firewall.go b/go/cmd/entrypoint/vpn_firewall.go new file mode 100644 index 0000000..2ad23ba --- /dev/null +++ b/go/cmd/entrypoint/vpn_firewall.go @@ -0,0 +1,64 @@ +package main + +import ( + "fmt" + "isle/daemon/daecommon" +) + +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) { + ctx, err := ctx.withParsedFlags() + if err != nil { + return nil, fmt.Errorf("parsing flags: %w", err) + } + + config, err := ctx.getDaemonRPC().GetConfig(ctx) + if err != nil { + return nil, fmt.Errorf("getting network config: %w", err) + } + + return newFirewallView(config.VPN.Firewall), nil + }), +} + +var subCmdVPNFirewall = subCmd{ + name: "firewall", + descr: "Sub-commands related to this host's VPN firewall", + do: func(ctx subCmdCtx) error { + return ctx.doSubCmd( + subCmdVPNFirewallList, + ) + }, +} diff --git a/go/cmd/entrypoint/vpn_firewall_test.go b/go/cmd/entrypoint/vpn_firewall_test.go new file mode 100644 index 0000000..2757656 --- /dev/null +++ b/go/cmd/entrypoint/vpn_firewall_test.go @@ -0,0 +1,108 @@ +package main + +import ( + "context" + "encoding/json" + "isle/daemon/daecommon" + "isle/toolkit" + "strings" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestVPNFirewallList(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + outbound, inbound []string + want map[string][]any + }{ + { + name: "empty", + want: map[string][]any{ + "outbound": {}, + "inbound": {}, + }, + }, + { + name: "single", + outbound: []string{ + `{"port":"any","proto":"icmp","host":"any"}`, + }, + want: map[string][]any{ + "outbound": { + map[string]any{ + "index": 0, + "port": "any", + "proto": "icmp", + "host": "any", + }, + }, + "inbound": {}, + }, + }, + { + name: "multiple", + outbound: []string{ + `{"port":"any","proto":"icmp","host":"any"}`, + }, + inbound: []string{ + `{"port":"any","proto":"icmp","host":"any"}`, + `{"port":"22","proto":"tcp","host":"foo"}`, + }, + want: map[string][]any{ + "outbound": { + map[string]any{ + "index": 0, + "port": "any", + "proto": "icmp", + "host": "any", + }, + }, + "inbound": { + map[string]any{ + "index": 0, + "port": "any", + "proto": "icmp", + "host": "any", + }, + map[string]any{ + "index": 1, + "port": "22", + "proto": "tcp", + "host": "foo", + }, + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + var ( + h = newRunHarness(t) + config daecommon.NetworkConfig + + outboundRawJSON = "[" + strings.Join(test.outbound, ",") + "]" + inboundRawJSON = "[" + strings.Join(test.inbound, ",") + "]" + ) + + require.NoError(t, json.Unmarshal( + []byte(outboundRawJSON), &config.VPN.Firewall.Outbound, + )) + + require.NoError(t, json.Unmarshal( + []byte(inboundRawJSON), &config.VPN.Firewall.Inbound, + )) + + h.daemonRPC. + On("GetConfig", toolkit.MockArg[context.Context]()). + Return(config, nil). + Once() + + h.runAssertStdout(t, test.want, "vpn", "firewall", "list") + }) + } +}