Compare commits

...

1 Commits

Author SHA1 Message Date
Maycon Santos
355bab9bb4 set command with cursor 2025-07-02 12:20:43 +02:00
8 changed files with 3369 additions and 939 deletions

View File

@@ -134,3 +134,37 @@ We use open-source technologies like [WireGuard®](https://www.wireguard.com/),
### Legal
_WireGuard_ and the _WireGuard_ logo are [registered trademarks](https://www.wireguard.com/trademark-policy/) of Jason A. Donenfeld.
## Configuration Management
Netbird now supports direct configuration management via CLI commands:
- You can use `netbird set` as a regular user if the daemon is running; it will securely update the config via the daemon.
- If the daemon is not running, you need write access to the config file (typically requires root).
### Set a configuration value
```
netbird set <setting> <value>
# or using environment variables
NB_INTERFACE_NAME=utun5 netbird set interface-name
```
### Get a configuration value
```
netbird get <setting>
# or using environment variables
NB_INTERFACE_NAME=utun5 netbird get interface-name
```
### Show all configuration values
```
netbird show
```
- All settings support environment variable overrides: `NB_<SETTING>` or `WT_<SETTING>` (e.g. `NB_ENABLE_ROSENPASS=true`).
- Supported settings: management-url, admin-url, interface-name, external-ip-map, extra-iface-blacklist, dns-resolver-address, extra-dns-labels, preshared-key, enable-rosenpass, rosenpass-permissive, allow-server-ssh, network-monitor, disable-auto-connect, disable-client-routes, disable-server-routes, disable-dns, disable-firewall, block-lan-access, block-inbound, enable-lazy-connection, wireguard-port, dns-router-interval.
See `netbird set --help`, `netbird get --help`, and `netbird show --help` for more details.

View File

@@ -22,6 +22,7 @@ import (
"google.golang.org/grpc/credentials/insecure"
"github.com/netbirdio/netbird/client/internal"
"github.com/netbirdio/netbird/client/proto"
"github.com/netbirdio/netbird/upload-server/types"
)
@@ -87,6 +88,30 @@ var (
Long: "",
SilenceUsage: true,
}
getCmd = &cobra.Command{
Use: "get <setting>",
Short: "Get a configuration value from the config file",
Long: `Get a configuration value from the Netbird config file. You can also use NB_<SETTING> or WT_<SETTING> environment variables to override the value (same as 'set').`,
Args: cobra.ExactArgs(1),
RunE: getFunc,
}
showCmd = &cobra.Command{
Use: "show",
Short: "Show all configuration values",
Long: `Show all configuration values from the Netbird config file, with environment variable overrides if present.`,
Args: cobra.NoArgs,
RunE: showFunc,
}
reloadCmd = &cobra.Command{
Use: "reload",
Short: "Reload the configuration in the daemon (daemon mode)",
Long: `Reload the configuration from disk in the running daemon. Use after 'set' to apply changes without restarting the service.`,
Args: cobra.NoArgs,
RunE: reloadFunc,
}
)
// Execute executes the root command.
@@ -152,6 +177,9 @@ func init() {
rootCmd.AddCommand(networksCMD)
rootCmd.AddCommand(forwardingRulesCmd)
rootCmd.AddCommand(debugCmd)
rootCmd.AddCommand(getCmd)
rootCmd.AddCommand(showCmd)
rootCmd.AddCommand(reloadCmd)
serviceCmd.AddCommand(runCmd, startCmd, stopCmd, restartCmd) // service control commands are subcommands of service
serviceCmd.AddCommand(installCmd, uninstallCmd) // service installer commands are subcommands of service
@@ -408,3 +436,167 @@ func getClient(cmd *cobra.Command) (*grpc.ClientConn, error) {
return conn, nil
}
func getFunc(cmd *cobra.Command, args []string) error {
setting := args[0]
upper := strings.ToUpper(strings.ReplaceAll(setting, "-", "_"))
if v, ok := os.LookupEnv("NB_" + upper); ok {
cmd.Println(v)
return nil
} else if v, ok := os.LookupEnv("WT_" + upper); ok {
cmd.Println(v)
return nil
}
config, err := internal.ReadConfig(configPath)
if err != nil {
return fmt.Errorf("failed to read config: %v", err)
}
switch setting {
case "management-url":
cmd.Println(config.ManagementURL.String())
case "admin-url":
cmd.Println(config.AdminURL.String())
case "interface-name":
cmd.Println(config.WgIface)
case "external-ip-map":
cmd.Println(strings.Join(config.NATExternalIPs, ","))
case "extra-iface-blacklist":
cmd.Println(strings.Join(config.IFaceBlackList, ","))
case "dns-resolver-address":
cmd.Println(config.CustomDNSAddress)
case "extra-dns-labels":
cmd.Println(config.DNSLabels.SafeString())
case "preshared-key":
cmd.Println(config.PreSharedKey)
case "enable-rosenpass":
cmd.Println(config.RosenpassEnabled)
case "rosenpass-permissive":
cmd.Println(config.RosenpassPermissive)
case "allow-server-ssh":
if config.ServerSSHAllowed != nil {
cmd.Println(*config.ServerSSHAllowed)
} else {
cmd.Println(false)
}
case "network-monitor":
if config.NetworkMonitor != nil {
cmd.Println(*config.NetworkMonitor)
} else {
cmd.Println(false)
}
case "disable-auto-connect":
cmd.Println(config.DisableAutoConnect)
case "disable-client-routes":
cmd.Println(config.DisableClientRoutes)
case "disable-server-routes":
cmd.Println(config.DisableServerRoutes)
case "disable-dns":
cmd.Println(config.DisableDNS)
case "disable-firewall":
cmd.Println(config.DisableFirewall)
case "block-lan-access":
cmd.Println(config.BlockLANAccess)
case "block-inbound":
cmd.Println(config.BlockInbound)
case "enable-lazy-connection":
cmd.Println(config.LazyConnectionEnabled)
case "wireguard-port":
cmd.Println(config.WgPort)
case "dns-router-interval":
cmd.Println(config.DNSRouteInterval)
default:
return fmt.Errorf("unknown setting: %s", setting)
}
return nil
}
func showFunc(cmd *cobra.Command, args []string) error {
config, err := internal.ReadConfig(configPath)
if err != nil {
return fmt.Errorf("failed to read config: %v", err)
}
settings := []string{
"management-url", "admin-url", "interface-name", "external-ip-map", "extra-iface-blacklist", "dns-resolver-address", "extra-dns-labels", "preshared-key", "enable-rosenpass", "rosenpass-permissive", "allow-server-ssh", "network-monitor", "disable-auto-connect", "disable-client-routes", "disable-server-routes", "disable-dns", "disable-firewall", "block-lan-access", "block-inbound", "enable-lazy-connection", "wireguard-port", "dns-router-interval",
}
for _, setting := range settings {
upper := strings.ToUpper(strings.ReplaceAll(setting, "-", "_"))
var val string
if v, ok := os.LookupEnv("NB_" + upper); ok {
val = v + " (from NB_ env)"
} else if v, ok := os.LookupEnv("WT_" + upper); ok {
val = v + " (from WT_ env)"
} else {
switch setting {
case "management-url":
val = config.ManagementURL.String()
case "admin-url":
val = config.AdminURL.String()
case "interface-name":
val = config.WgIface
case "external-ip-map":
val = strings.Join(config.NATExternalIPs, ",")
case "extra-iface-blacklist":
val = strings.Join(config.IFaceBlackList, ",")
case "dns-resolver-address":
val = config.CustomDNSAddress
case "extra-dns-labels":
val = config.DNSLabels.SafeString()
case "preshared-key":
val = config.PreSharedKey
case "enable-rosenpass":
val = fmt.Sprintf("%v", config.RosenpassEnabled)
case "rosenpass-permissive":
val = fmt.Sprintf("%v", config.RosenpassPermissive)
case "allow-server-ssh":
if config.ServerSSHAllowed != nil {
val = fmt.Sprintf("%v", *config.ServerSSHAllowed)
} else {
val = "false"
}
case "network-monitor":
if config.NetworkMonitor != nil {
val = fmt.Sprintf("%v", *config.NetworkMonitor)
} else {
val = "false"
}
case "disable-auto-connect":
val = fmt.Sprintf("%v", config.DisableAutoConnect)
case "disable-client-routes":
val = fmt.Sprintf("%v", config.DisableClientRoutes)
case "disable-server-routes":
val = fmt.Sprintf("%v", config.DisableServerRoutes)
case "disable-dns":
val = fmt.Sprintf("%v", config.DisableDNS)
case "disable-firewall":
val = fmt.Sprintf("%v", config.DisableFirewall)
case "block-lan-access":
val = fmt.Sprintf("%v", config.BlockLANAccess)
case "block-inbound":
val = fmt.Sprintf("%v", config.BlockInbound)
case "enable-lazy-connection":
val = fmt.Sprintf("%v", config.LazyConnectionEnabled)
case "wireguard-port":
val = fmt.Sprintf("%d", config.WgPort)
case "dns-router-interval":
val = config.DNSRouteInterval.String()
}
}
cmd.Printf("%-22s: %s\n", setting, val)
}
return nil
}
func reloadFunc(cmd *cobra.Command, args []string) error {
conn, err := getClient(cmd)
if err != nil {
return err
}
defer conn.Close()
client := proto.NewDaemonServiceClient(conn)
_, err = client.ReloadConfig(cmd.Context(), &proto.ReloadConfigRequest{})
if err != nil {
return fmt.Errorf("failed to reload config in daemon: %v", err)
}
cmd.Println("Configuration reloaded in daemon.")
return nil
}

475
client/cmd/set.go Normal file
View File

@@ -0,0 +1,475 @@
package cmd
import (
"fmt"
"os"
osuser "os/user"
"strings"
"time"
"github.com/netbirdio/netbird/client/internal"
"github.com/netbirdio/netbird/client/proto"
"github.com/netbirdio/netbird/management/domain"
"github.com/spf13/cobra"
"google.golang.org/grpc/status"
)
var setCmd = &cobra.Command{
Use: "set <setting> <value>",
Short: "Set a configuration value without running up",
Long: `Set a configuration value in the Netbird config file without running 'up'.
You can also set values via environment variables NB_<SETTING> or WT_<SETTING> (e.g. NB_INTERFACE_NAME=utun5 netbird set interface-name).
Supported settings:
management-url (string) e.g. https://api.netbird.io:443
admin-url (string) e.g. https://app.netbird.io:443
interface-name (string) e.g. utun5
external-ip-map (list) comma-separated, e.g. 12.34.56.78,12.34.56.79/eth0
extra-iface-blacklist (list) comma-separated, e.g. eth1,eth2
dns-resolver-address (string) e.g. 127.0.0.1:5053
extra-dns-labels (list) comma-separated, e.g. vpc1,mgmt1
preshared-key (string)
enable-rosenpass (bool) true/false
rosenpass-permissive (bool) true/false
allow-server-ssh (bool) true/false
network-monitor (bool) true/false
disable-auto-connect (bool) true/false
disable-client-routes (bool) true/false
disable-server-routes (bool) true/false
disable-dns (bool) true/false
disable-firewall (bool) true/false
block-lan-access (bool) true/false
block-inbound (bool) true/false
enable-lazy-connection (bool) true/false
wireguard-port (int) e.g. 51820
dns-router-interval (duration) e.g. 1m, 30s
Examples:
NB_INTERFACE_NAME=utun5 netbird set interface-name
netbird set wireguard-port 51820
netbird set external-ip-map 12.34.56.78,12.34.56.79/eth0
netbird set enable-rosenpass true
netbird set dns-router-interval 2m
netbird set extra-dns-labels vpc1,mgmt1
netbird set disable-firewall true
`,
Args: cobra.ExactArgs(2),
RunE: setFunc,
}
func init() {
rootCmd.AddCommand(setCmd)
}
func setFunc(cmd *cobra.Command, args []string) error {
setting := args[0]
var value string
// Check environment variables first
upper := strings.ToUpper(strings.ReplaceAll(setting, "-", "_"))
if v, ok := os.LookupEnv("NB_" + upper); ok {
value = v
} else if v, ok := os.LookupEnv("WT_" + upper); ok {
value = v
} else {
if len(args) < 2 {
return fmt.Errorf("missing value for setting %s", setting)
}
value = args[1]
}
// If not root, try to use the daemon (only if cmd is not nil)
if cmd != nil {
if u, err := osuser.Current(); err == nil && u.Uid != "0" {
conn, err := getClient(cmd)
if err == nil {
defer conn.Close()
client := proto.NewDaemonServiceClient(conn)
_, err = client.SetConfigValue(cmd.Context(), &proto.SetConfigValueRequest{Setting: setting, Value: value})
if err == nil {
if cmd != nil {
cmd.Println("Configuration updated via daemon.")
} else {
fmt.Println("Configuration updated via daemon.")
}
return nil
}
if s, ok := status.FromError(err); ok {
return fmt.Errorf("daemon error: %v", s.Message())
}
return fmt.Errorf("failed to update config via daemon: %v", err)
}
// else: fall back to direct file write
}
}
switch setting {
case "management-url":
input := internal.ConfigInput{ConfigPath: configPath, ManagementURL: value}
_, err := internal.UpdateOrCreateConfig(input)
if err != nil {
return fmt.Errorf("failed to set management-url: %v", err)
}
if cmd != nil {
cmd.Printf("Set management-url to: %s\n", value)
} else {
fmt.Printf("Set management-url to: %s\n", value)
}
case "admin-url":
input := internal.ConfigInput{ConfigPath: configPath, AdminURL: value}
_, err := internal.UpdateOrCreateConfig(input)
if err != nil {
return fmt.Errorf("failed to set admin-url: %v", err)
}
if cmd != nil {
cmd.Printf("Set admin-url to: %s\n", value)
} else {
fmt.Printf("Set admin-url to: %s\n", value)
}
case "interface-name":
if err := parseInterfaceName(value); err != nil {
return err
}
input := internal.ConfigInput{ConfigPath: configPath, InterfaceName: &value}
_, err := internal.UpdateOrCreateConfig(input)
if err != nil {
return fmt.Errorf("failed to set interface-name: %v", err)
}
if cmd != nil {
cmd.Printf("Set interface-name to: %s\n", value)
} else {
fmt.Printf("Set interface-name to: %s\n", value)
}
case "external-ip-map":
var ips []string
if value == "" {
ips = []string{}
} else {
ips = strings.Split(value, ",")
}
if err := validateNATExternalIPs(ips); err != nil {
return err
}
input := internal.ConfigInput{ConfigPath: configPath, NATExternalIPs: ips}
_, err := internal.UpdateOrCreateConfig(input)
if err != nil {
return fmt.Errorf("failed to set external-ip-map: %v", err)
}
if cmd != nil {
cmd.Printf("Set external-ip-map to: %v\n", ips)
} else {
fmt.Printf("Set external-ip-map to: %v\n", ips)
}
case "extra-iface-blacklist":
var ifaces []string
if value == "" {
ifaces = []string{}
} else {
ifaces = strings.Split(value, ",")
}
input := internal.ConfigInput{ConfigPath: configPath, ExtraIFaceBlackList: ifaces}
_, err := internal.UpdateOrCreateConfig(input)
if err != nil {
return fmt.Errorf("failed to set extra-iface-blacklist: %v", err)
}
if cmd != nil {
cmd.Printf("Set extra-iface-blacklist to: %v\n", ifaces)
} else {
fmt.Printf("Set extra-iface-blacklist to: %v\n", ifaces)
}
case "dns-resolver-address":
if value != "" && !isValidAddrPort(value) {
return fmt.Errorf("%s is invalid, it should be formatted as IP:Port string or as an empty string like \"\"", value)
}
input := internal.ConfigInput{ConfigPath: configPath, CustomDNSAddress: []byte(value)}
_, err := internal.UpdateOrCreateConfig(input)
if err != nil {
return fmt.Errorf("failed to set dns-resolver-address: %v", err)
}
if cmd != nil {
cmd.Printf("Set dns-resolver-address to: %s\n", value)
} else {
fmt.Printf("Set dns-resolver-address to: %s\n", value)
}
case "extra-dns-labels":
var labels []string
if value == "" {
labels = []string{}
} else {
labels = strings.Split(value, ",")
}
domains, err := domain.ValidateDomains(labels)
if err != nil {
return fmt.Errorf("invalid DNS labels: %v", err)
}
input := internal.ConfigInput{ConfigPath: configPath, DNSLabels: domains}
_, err = internal.UpdateOrCreateConfig(input)
if err != nil {
return fmt.Errorf("failed to set extra-dns-labels: %v", err)
}
if cmd != nil {
cmd.Printf("Set extra-dns-labels to: %v\n", labels)
} else {
fmt.Printf("Set extra-dns-labels to: %v\n", labels)
}
case "preshared-key":
input := internal.ConfigInput{ConfigPath: configPath, PreSharedKey: &value}
_, err := internal.UpdateOrCreateConfig(input)
if err != nil {
return fmt.Errorf("failed to set preshared-key: %v", err)
}
if cmd != nil {
cmd.Printf("Set preshared-key to: %s\n", value)
} else {
fmt.Printf("Set preshared-key to: %s\n", value)
}
case "hostname":
// Hostname is not persisted in config, so just print a warning
if cmd != nil {
cmd.Printf("Warning: hostname is not persisted in config. Use --hostname with up command.\n")
} else {
fmt.Printf("Warning: hostname is not persisted in config. Use --hostname with up command.\n")
}
case "enable-rosenpass":
b, err := parseBool(value)
if err != nil {
return err
}
input := internal.ConfigInput{ConfigPath: configPath, RosenpassEnabled: &b}
_, err = internal.UpdateOrCreateConfig(input)
if err != nil {
return fmt.Errorf("failed to set enable-rosenpass: %v", err)
}
if cmd != nil {
cmd.Printf("Set enable-rosenpass to: %v\n", b)
} else {
fmt.Printf("Set enable-rosenpass to: %v\n", b)
}
case "rosenpass-permissive":
b, err := parseBool(value)
if err != nil {
return err
}
input := internal.ConfigInput{ConfigPath: configPath, RosenpassPermissive: &b}
_, err = internal.UpdateOrCreateConfig(input)
if err != nil {
return fmt.Errorf("failed to set rosenpass-permissive: %v", err)
}
if cmd != nil {
cmd.Printf("Set rosenpass-permissive to: %v\n", b)
} else {
fmt.Printf("Set rosenpass-permissive to: %v\n", b)
}
case "allow-server-ssh":
b, err := parseBool(value)
if err != nil {
return err
}
input := internal.ConfigInput{ConfigPath: configPath, ServerSSHAllowed: &b}
_, err = internal.UpdateOrCreateConfig(input)
if err != nil {
return fmt.Errorf("failed to set allow-server-ssh: %v", err)
}
if cmd != nil {
cmd.Printf("Set allow-server-ssh to: %v\n", b)
} else {
fmt.Printf("Set allow-server-ssh to: %v\n", b)
}
case "network-monitor":
b, err := parseBool(value)
if err != nil {
return err
}
input := internal.ConfigInput{ConfigPath: configPath, NetworkMonitor: &b}
_, err = internal.UpdateOrCreateConfig(input)
if err != nil {
return fmt.Errorf("failed to set network-monitor: %v", err)
}
if cmd != nil {
cmd.Printf("Set network-monitor to: %v\n", b)
} else {
fmt.Printf("Set network-monitor to: %v\n", b)
}
case "disable-auto-connect":
b, err := parseBool(value)
if err != nil {
return err
}
input := internal.ConfigInput{ConfigPath: configPath, DisableAutoConnect: &b}
_, err = internal.UpdateOrCreateConfig(input)
if err != nil {
return fmt.Errorf("failed to set disable-auto-connect: %v", err)
}
if cmd != nil {
cmd.Printf("Set disable-auto-connect to: %v\n", b)
} else {
fmt.Printf("Set disable-auto-connect to: %v\n", b)
}
case "disable-client-routes":
b, err := parseBool(value)
if err != nil {
return err
}
input := internal.ConfigInput{ConfigPath: configPath, DisableClientRoutes: &b}
_, err = internal.UpdateOrCreateConfig(input)
if err != nil {
return fmt.Errorf("failed to set disable-client-routes: %v", err)
}
if cmd != nil {
cmd.Printf("Set disable-client-routes to: %v\n", b)
} else {
fmt.Printf("Set disable-client-routes to: %v\n", b)
}
case "disable-server-routes":
b, err := parseBool(value)
if err != nil {
return err
}
input := internal.ConfigInput{ConfigPath: configPath, DisableServerRoutes: &b}
_, err = internal.UpdateOrCreateConfig(input)
if err != nil {
return fmt.Errorf("failed to set disable-server-routes: %v", err)
}
if cmd != nil {
cmd.Printf("Set disable-server-routes to: %v\n", b)
} else {
fmt.Printf("Set disable-server-routes to: %v\n", b)
}
case "disable-dns":
b, err := parseBool(value)
if err != nil {
return err
}
input := internal.ConfigInput{ConfigPath: configPath, DisableDNS: &b}
_, err = internal.UpdateOrCreateConfig(input)
if err != nil {
return fmt.Errorf("failed to set disable-dns: %v", err)
}
if cmd != nil {
cmd.Printf("Set disable-dns to: %v\n", b)
} else {
fmt.Printf("Set disable-dns to: %v\n", b)
}
case "disable-firewall":
b, err := parseBool(value)
if err != nil {
return err
}
input := internal.ConfigInput{ConfigPath: configPath, DisableFirewall: &b}
_, err = internal.UpdateOrCreateConfig(input)
if err != nil {
return fmt.Errorf("failed to set disable-firewall: %v", err)
}
if cmd != nil {
cmd.Printf("Set disable-firewall to: %v\n", b)
} else {
fmt.Printf("Set disable-firewall to: %v\n", b)
}
case "block-lan-access":
b, err := parseBool(value)
if err != nil {
return err
}
input := internal.ConfigInput{ConfigPath: configPath, BlockLANAccess: &b}
_, err = internal.UpdateOrCreateConfig(input)
if err != nil {
return fmt.Errorf("failed to set block-lan-access: %v", err)
}
if cmd != nil {
cmd.Printf("Set block-lan-access to: %v\n", b)
} else {
fmt.Printf("Set block-lan-access to: %v\n", b)
}
case "block-inbound":
b, err := parseBool(value)
if err != nil {
return err
}
input := internal.ConfigInput{ConfigPath: configPath, BlockInbound: &b}
_, err = internal.UpdateOrCreateConfig(input)
if err != nil {
return fmt.Errorf("failed to set block-inbound: %v", err)
}
if cmd != nil {
cmd.Printf("Set block-inbound to: %v\n", b)
} else {
fmt.Printf("Set block-inbound to: %v\n", b)
}
case "enable-lazy-connection":
b, err := parseBool(value)
if err != nil {
return err
}
input := internal.ConfigInput{ConfigPath: configPath, LazyConnectionEnabled: &b}
_, err = internal.UpdateOrCreateConfig(input)
if err != nil {
return fmt.Errorf("failed to set enable-lazy-connection: %v", err)
}
if cmd != nil {
cmd.Printf("Set enable-lazy-connection to: %v\n", b)
} else {
fmt.Printf("Set enable-lazy-connection to: %v\n", b)
}
case "wireguard-port":
p, err := parseUint16(value)
if err != nil {
return err
}
pi := int(p)
input := internal.ConfigInput{ConfigPath: configPath, WireguardPort: &pi}
_, err = internal.UpdateOrCreateConfig(input)
if err != nil {
return fmt.Errorf("failed to set wireguard-port: %v", err)
}
if cmd != nil {
cmd.Printf("Set wireguard-port to: %d\n", p)
} else {
fmt.Printf("Set wireguard-port to: %d\n", p)
}
case "dns-router-interval":
d, err := time.ParseDuration(value)
if err != nil {
return fmt.Errorf("invalid duration: %v", err)
}
input := internal.ConfigInput{ConfigPath: configPath, DNSRouteInterval: &d}
_, err = internal.UpdateOrCreateConfig(input)
if err != nil {
return fmt.Errorf("failed to set dns-router-interval: %v", err)
}
if cmd != nil {
cmd.Printf("Set dns-router-interval to: %s\n", d)
} else {
fmt.Printf("Set dns-router-interval to: %s\n", d)
}
default:
return fmt.Errorf("unknown setting: %s", setting)
}
if cmd != nil {
cmd.Println("Configuration updated successfully.")
} else {
fmt.Println("Configuration updated successfully.")
}
return nil
}
func parseBool(val string) (bool, error) {
v := strings.ToLower(val)
if v == "true" || v == "1" {
return true, nil
}
if v == "false" || v == "0" {
return false, nil
}
return false, fmt.Errorf("invalid boolean value: %s", val)
}
func parseUint16(val string) (uint16, error) {
var p uint16
_, err := fmt.Sscanf(val, "%d", &p)
if err != nil {
return 0, fmt.Errorf("invalid uint16 value: %s", val)
}
return p, nil
}

162
client/cmd/set_test.go Normal file
View File

@@ -0,0 +1,162 @@
package cmd
import (
"os"
"strings"
"testing"
"time"
"github.com/netbirdio/netbird/client/internal"
"github.com/stretchr/testify/require"
)
func TestSetCommand_AllSettings(t *testing.T) {
tempFile, err := os.CreateTemp("", "config.json")
require.NoError(t, err)
defer os.Remove(tempFile.Name())
// Write empty JSON object to the config file to avoid JSON parse errors
_, err = tempFile.WriteString("{}")
require.NoError(t, err)
tempFile.Close()
configPath = tempFile.Name()
tests := []struct {
setting string
value string
verify func(*testing.T, *internal.Config)
wantErr bool
}{
{"management-url", "https://test.mgmt:443", func(t *testing.T, c *internal.Config) {
require.Equal(t, "https://test.mgmt:443", c.ManagementURL.String())
}, false},
{"admin-url", "https://test.admin:443", func(t *testing.T, c *internal.Config) {
require.Equal(t, "https://test.admin:443", c.AdminURL.String())
}, false},
{"interface-name", "utun99", func(t *testing.T, c *internal.Config) {
require.Equal(t, "utun99", c.WgIface)
}, false},
{"external-ip-map", "12.34.56.78,12.34.56.79", func(t *testing.T, c *internal.Config) {
require.Equal(t, []string{"12.34.56.78", "12.34.56.79"}, c.NATExternalIPs)
}, false},
{"extra-iface-blacklist", "eth1,eth2", func(t *testing.T, c *internal.Config) {
require.Contains(t, c.IFaceBlackList, "eth1")
require.Contains(t, c.IFaceBlackList, "eth2")
}, false},
{"dns-resolver-address", "127.0.0.1:5053", func(t *testing.T, c *internal.Config) {
require.Equal(t, "127.0.0.1:5053", c.CustomDNSAddress)
}, false},
{"extra-dns-labels", "vpc1,mgmt1", func(t *testing.T, c *internal.Config) {
require.True(t, strings.Contains(c.DNSLabels.SafeString(), "vpc1"))
require.True(t, strings.Contains(c.DNSLabels.SafeString(), "mgmt1"))
}, false},
{"preshared-key", "testkey", func(t *testing.T, c *internal.Config) {
require.Equal(t, "testkey", c.PreSharedKey)
}, false},
{"enable-rosenpass", "true", func(t *testing.T, c *internal.Config) {
require.True(t, c.RosenpassEnabled)
}, false},
{"rosenpass-permissive", "false", func(t *testing.T, c *internal.Config) {
require.False(t, c.RosenpassPermissive)
}, false},
{"allow-server-ssh", "true", func(t *testing.T, c *internal.Config) {
require.NotNil(t, c.ServerSSHAllowed)
require.True(t, *c.ServerSSHAllowed)
}, false},
{"network-monitor", "false", func(t *testing.T, c *internal.Config) {
require.NotNil(t, c.NetworkMonitor)
require.False(t, *c.NetworkMonitor)
}, false},
{"disable-auto-connect", "true", func(t *testing.T, c *internal.Config) {
require.True(t, c.DisableAutoConnect)
}, false},
{"disable-client-routes", "false", func(t *testing.T, c *internal.Config) {
require.False(t, c.DisableClientRoutes)
}, false},
{"disable-server-routes", "true", func(t *testing.T, c *internal.Config) {
require.True(t, c.DisableServerRoutes)
}, false},
{"disable-dns", "false", func(t *testing.T, c *internal.Config) {
require.False(t, c.DisableDNS)
}, false},
{"disable-firewall", "true", func(t *testing.T, c *internal.Config) {
require.True(t, c.DisableFirewall)
}, false},
{"block-lan-access", "true", func(t *testing.T, c *internal.Config) {
require.True(t, c.BlockLANAccess)
}, false},
{"block-inbound", "false", func(t *testing.T, c *internal.Config) {
require.False(t, c.BlockInbound)
}, false},
{"enable-lazy-connection", "true", func(t *testing.T, c *internal.Config) {
require.True(t, c.LazyConnectionEnabled)
}, false},
{"wireguard-port", "51820", func(t *testing.T, c *internal.Config) {
require.Equal(t, 51820, c.WgPort)
}, false},
{"dns-router-interval", "2m", func(t *testing.T, c *internal.Config) {
require.Equal(t, 2*time.Minute, c.DNSRouteInterval)
}, false},
// Invalid cases
{"enable-rosenpass", "notabool", nil, true},
{"wireguard-port", "notanint", nil, true},
{"dns-router-interval", "notaduration", nil, true},
}
for _, tt := range tests {
t.Run(tt.setting+"="+tt.value, func(t *testing.T) {
args := []string{tt.setting, tt.value}
err := setFunc(nil, args)
if tt.wantErr {
require.Error(t, err)
return
}
require.NoError(t, err)
config, err := internal.ReadConfig(configPath)
require.NoError(t, err)
if tt.verify != nil {
tt.verify(t, config)
}
})
}
}
func TestSetCommand_EnvVars(t *testing.T) {
tempFile, err := os.CreateTemp("", "config.json")
require.NoError(t, err)
defer os.Remove(tempFile.Name())
// Write empty JSON object to the config file to avoid JSON parse errors
_, err = tempFile.WriteString("{}")
require.NoError(t, err)
tempFile.Close()
configPath = tempFile.Name()
os.Setenv("NB_INTERFACE_NAME", "utun77")
defer os.Unsetenv("NB_INTERFACE_NAME")
args := []string{"interface-name", "utun99"}
err = setFunc(nil, args)
require.NoError(t, err)
config, err := internal.ReadConfig(configPath)
require.NoError(t, err)
require.Equal(t, "utun77", config.WgIface)
os.Unsetenv("NB_INTERFACE_NAME")
os.Setenv("WT_INTERFACE_NAME", "utun88")
defer os.Unsetenv("WT_INTERFACE_NAME")
err = setFunc(nil, args)
require.NoError(t, err)
config, err = internal.ReadConfig(configPath)
require.NoError(t, err)
require.Equal(t, "utun88", config.WgIface)
os.Unsetenv("WT_INTERFACE_NAME")
// No env var, should use CLI value
err = setFunc(nil, args)
require.NoError(t, err)
config, err = internal.ReadConfig(configPath)
require.NoError(t, err)
require.Equal(t, "utun99", config.WgIface)
}

File diff suppressed because it is too large Load Diff

View File

@@ -67,6 +67,12 @@ service DaemonService {
rpc SubscribeEvents(SubscribeRequest) returns (stream SystemEvent) {}
rpc GetEvents(GetEventsRequest) returns (GetEventsResponse) {}
// Reloads the configuration from disk
rpc ReloadConfig(ReloadConfigRequest) returns (ReloadConfigResponse) {}
// Sets a configuration value (for use by regular users via the daemon)
rpc SetConfigValue(SetConfigValueRequest) returns (SetConfigValueResponse) {}
}
@@ -496,3 +502,12 @@ message GetEventsRequest {}
message GetEventsResponse {
repeated SystemEvent events = 1;
}
message ReloadConfigRequest {}
message ReloadConfigResponse {}
message SetConfigValueRequest {
string setting = 1;
string value = 2;
}
message SetConfigValueResponse {}

View File

@@ -1,8 +1,4 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.5.1
// - protoc v5.29.3
// source: daemon.proto
package proto
@@ -15,31 +11,8 @@ import (
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.64.0 or later.
const _ = grpc.SupportPackageIsVersion9
const (
DaemonService_Login_FullMethodName = "/daemon.DaemonService/Login"
DaemonService_WaitSSOLogin_FullMethodName = "/daemon.DaemonService/WaitSSOLogin"
DaemonService_Up_FullMethodName = "/daemon.DaemonService/Up"
DaemonService_Status_FullMethodName = "/daemon.DaemonService/Status"
DaemonService_Down_FullMethodName = "/daemon.DaemonService/Down"
DaemonService_GetConfig_FullMethodName = "/daemon.DaemonService/GetConfig"
DaemonService_ListNetworks_FullMethodName = "/daemon.DaemonService/ListNetworks"
DaemonService_SelectNetworks_FullMethodName = "/daemon.DaemonService/SelectNetworks"
DaemonService_DeselectNetworks_FullMethodName = "/daemon.DaemonService/DeselectNetworks"
DaemonService_ForwardingRules_FullMethodName = "/daemon.DaemonService/ForwardingRules"
DaemonService_DebugBundle_FullMethodName = "/daemon.DaemonService/DebugBundle"
DaemonService_GetLogLevel_FullMethodName = "/daemon.DaemonService/GetLogLevel"
DaemonService_SetLogLevel_FullMethodName = "/daemon.DaemonService/SetLogLevel"
DaemonService_ListStates_FullMethodName = "/daemon.DaemonService/ListStates"
DaemonService_CleanState_FullMethodName = "/daemon.DaemonService/CleanState"
DaemonService_DeleteState_FullMethodName = "/daemon.DaemonService/DeleteState"
DaemonService_SetNetworkMapPersistence_FullMethodName = "/daemon.DaemonService/SetNetworkMapPersistence"
DaemonService_TracePacket_FullMethodName = "/daemon.DaemonService/TracePacket"
DaemonService_SubscribeEvents_FullMethodName = "/daemon.DaemonService/SubscribeEvents"
DaemonService_GetEvents_FullMethodName = "/daemon.DaemonService/GetEvents"
)
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
// DaemonServiceClient is the client API for DaemonService service.
//
@@ -80,8 +53,12 @@ type DaemonServiceClient interface {
// SetNetworkMapPersistence enables or disables network map persistence
SetNetworkMapPersistence(ctx context.Context, in *SetNetworkMapPersistenceRequest, opts ...grpc.CallOption) (*SetNetworkMapPersistenceResponse, error)
TracePacket(ctx context.Context, in *TracePacketRequest, opts ...grpc.CallOption) (*TracePacketResponse, error)
SubscribeEvents(ctx context.Context, in *SubscribeRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[SystemEvent], error)
SubscribeEvents(ctx context.Context, in *SubscribeRequest, opts ...grpc.CallOption) (DaemonService_SubscribeEventsClient, error)
GetEvents(ctx context.Context, in *GetEventsRequest, opts ...grpc.CallOption) (*GetEventsResponse, error)
// Reloads the configuration from disk
ReloadConfig(ctx context.Context, in *ReloadConfigRequest, opts ...grpc.CallOption) (*ReloadConfigResponse, error)
// Sets a configuration value (for use by regular users via the daemon)
SetConfigValue(ctx context.Context, in *SetConfigValueRequest, opts ...grpc.CallOption) (*SetConfigValueResponse, error)
}
type daemonServiceClient struct {
@@ -93,9 +70,8 @@ func NewDaemonServiceClient(cc grpc.ClientConnInterface) DaemonServiceClient {
}
func (c *daemonServiceClient) Login(ctx context.Context, in *LoginRequest, opts ...grpc.CallOption) (*LoginResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(LoginResponse)
err := c.cc.Invoke(ctx, DaemonService_Login_FullMethodName, in, out, cOpts...)
err := c.cc.Invoke(ctx, "/daemon.DaemonService/Login", in, out, opts...)
if err != nil {
return nil, err
}
@@ -103,9 +79,8 @@ func (c *daemonServiceClient) Login(ctx context.Context, in *LoginRequest, opts
}
func (c *daemonServiceClient) WaitSSOLogin(ctx context.Context, in *WaitSSOLoginRequest, opts ...grpc.CallOption) (*WaitSSOLoginResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(WaitSSOLoginResponse)
err := c.cc.Invoke(ctx, DaemonService_WaitSSOLogin_FullMethodName, in, out, cOpts...)
err := c.cc.Invoke(ctx, "/daemon.DaemonService/WaitSSOLogin", in, out, opts...)
if err != nil {
return nil, err
}
@@ -113,9 +88,8 @@ func (c *daemonServiceClient) WaitSSOLogin(ctx context.Context, in *WaitSSOLogin
}
func (c *daemonServiceClient) Up(ctx context.Context, in *UpRequest, opts ...grpc.CallOption) (*UpResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(UpResponse)
err := c.cc.Invoke(ctx, DaemonService_Up_FullMethodName, in, out, cOpts...)
err := c.cc.Invoke(ctx, "/daemon.DaemonService/Up", in, out, opts...)
if err != nil {
return nil, err
}
@@ -123,9 +97,8 @@ func (c *daemonServiceClient) Up(ctx context.Context, in *UpRequest, opts ...grp
}
func (c *daemonServiceClient) Status(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*StatusResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(StatusResponse)
err := c.cc.Invoke(ctx, DaemonService_Status_FullMethodName, in, out, cOpts...)
err := c.cc.Invoke(ctx, "/daemon.DaemonService/Status", in, out, opts...)
if err != nil {
return nil, err
}
@@ -133,9 +106,8 @@ func (c *daemonServiceClient) Status(ctx context.Context, in *StatusRequest, opt
}
func (c *daemonServiceClient) Down(ctx context.Context, in *DownRequest, opts ...grpc.CallOption) (*DownResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(DownResponse)
err := c.cc.Invoke(ctx, DaemonService_Down_FullMethodName, in, out, cOpts...)
err := c.cc.Invoke(ctx, "/daemon.DaemonService/Down", in, out, opts...)
if err != nil {
return nil, err
}
@@ -143,9 +115,8 @@ func (c *daemonServiceClient) Down(ctx context.Context, in *DownRequest, opts ..
}
func (c *daemonServiceClient) GetConfig(ctx context.Context, in *GetConfigRequest, opts ...grpc.CallOption) (*GetConfigResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(GetConfigResponse)
err := c.cc.Invoke(ctx, DaemonService_GetConfig_FullMethodName, in, out, cOpts...)
err := c.cc.Invoke(ctx, "/daemon.DaemonService/GetConfig", in, out, opts...)
if err != nil {
return nil, err
}
@@ -153,9 +124,8 @@ func (c *daemonServiceClient) GetConfig(ctx context.Context, in *GetConfigReques
}
func (c *daemonServiceClient) ListNetworks(ctx context.Context, in *ListNetworksRequest, opts ...grpc.CallOption) (*ListNetworksResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ListNetworksResponse)
err := c.cc.Invoke(ctx, DaemonService_ListNetworks_FullMethodName, in, out, cOpts...)
err := c.cc.Invoke(ctx, "/daemon.DaemonService/ListNetworks", in, out, opts...)
if err != nil {
return nil, err
}
@@ -163,9 +133,8 @@ func (c *daemonServiceClient) ListNetworks(ctx context.Context, in *ListNetworks
}
func (c *daemonServiceClient) SelectNetworks(ctx context.Context, in *SelectNetworksRequest, opts ...grpc.CallOption) (*SelectNetworksResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(SelectNetworksResponse)
err := c.cc.Invoke(ctx, DaemonService_SelectNetworks_FullMethodName, in, out, cOpts...)
err := c.cc.Invoke(ctx, "/daemon.DaemonService/SelectNetworks", in, out, opts...)
if err != nil {
return nil, err
}
@@ -173,9 +142,8 @@ func (c *daemonServiceClient) SelectNetworks(ctx context.Context, in *SelectNetw
}
func (c *daemonServiceClient) DeselectNetworks(ctx context.Context, in *SelectNetworksRequest, opts ...grpc.CallOption) (*SelectNetworksResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(SelectNetworksResponse)
err := c.cc.Invoke(ctx, DaemonService_DeselectNetworks_FullMethodName, in, out, cOpts...)
err := c.cc.Invoke(ctx, "/daemon.DaemonService/DeselectNetworks", in, out, opts...)
if err != nil {
return nil, err
}
@@ -183,9 +151,8 @@ func (c *daemonServiceClient) DeselectNetworks(ctx context.Context, in *SelectNe
}
func (c *daemonServiceClient) ForwardingRules(ctx context.Context, in *EmptyRequest, opts ...grpc.CallOption) (*ForwardingRulesResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ForwardingRulesResponse)
err := c.cc.Invoke(ctx, DaemonService_ForwardingRules_FullMethodName, in, out, cOpts...)
err := c.cc.Invoke(ctx, "/daemon.DaemonService/ForwardingRules", in, out, opts...)
if err != nil {
return nil, err
}
@@ -193,9 +160,8 @@ func (c *daemonServiceClient) ForwardingRules(ctx context.Context, in *EmptyRequ
}
func (c *daemonServiceClient) DebugBundle(ctx context.Context, in *DebugBundleRequest, opts ...grpc.CallOption) (*DebugBundleResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(DebugBundleResponse)
err := c.cc.Invoke(ctx, DaemonService_DebugBundle_FullMethodName, in, out, cOpts...)
err := c.cc.Invoke(ctx, "/daemon.DaemonService/DebugBundle", in, out, opts...)
if err != nil {
return nil, err
}
@@ -203,9 +169,8 @@ func (c *daemonServiceClient) DebugBundle(ctx context.Context, in *DebugBundleRe
}
func (c *daemonServiceClient) GetLogLevel(ctx context.Context, in *GetLogLevelRequest, opts ...grpc.CallOption) (*GetLogLevelResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(GetLogLevelResponse)
err := c.cc.Invoke(ctx, DaemonService_GetLogLevel_FullMethodName, in, out, cOpts...)
err := c.cc.Invoke(ctx, "/daemon.DaemonService/GetLogLevel", in, out, opts...)
if err != nil {
return nil, err
}
@@ -213,9 +178,8 @@ func (c *daemonServiceClient) GetLogLevel(ctx context.Context, in *GetLogLevelRe
}
func (c *daemonServiceClient) SetLogLevel(ctx context.Context, in *SetLogLevelRequest, opts ...grpc.CallOption) (*SetLogLevelResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(SetLogLevelResponse)
err := c.cc.Invoke(ctx, DaemonService_SetLogLevel_FullMethodName, in, out, cOpts...)
err := c.cc.Invoke(ctx, "/daemon.DaemonService/SetLogLevel", in, out, opts...)
if err != nil {
return nil, err
}
@@ -223,9 +187,8 @@ func (c *daemonServiceClient) SetLogLevel(ctx context.Context, in *SetLogLevelRe
}
func (c *daemonServiceClient) ListStates(ctx context.Context, in *ListStatesRequest, opts ...grpc.CallOption) (*ListStatesResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ListStatesResponse)
err := c.cc.Invoke(ctx, DaemonService_ListStates_FullMethodName, in, out, cOpts...)
err := c.cc.Invoke(ctx, "/daemon.DaemonService/ListStates", in, out, opts...)
if err != nil {
return nil, err
}
@@ -233,9 +196,8 @@ func (c *daemonServiceClient) ListStates(ctx context.Context, in *ListStatesRequ
}
func (c *daemonServiceClient) CleanState(ctx context.Context, in *CleanStateRequest, opts ...grpc.CallOption) (*CleanStateResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(CleanStateResponse)
err := c.cc.Invoke(ctx, DaemonService_CleanState_FullMethodName, in, out, cOpts...)
err := c.cc.Invoke(ctx, "/daemon.DaemonService/CleanState", in, out, opts...)
if err != nil {
return nil, err
}
@@ -243,9 +205,8 @@ func (c *daemonServiceClient) CleanState(ctx context.Context, in *CleanStateRequ
}
func (c *daemonServiceClient) DeleteState(ctx context.Context, in *DeleteStateRequest, opts ...grpc.CallOption) (*DeleteStateResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(DeleteStateResponse)
err := c.cc.Invoke(ctx, DaemonService_DeleteState_FullMethodName, in, out, cOpts...)
err := c.cc.Invoke(ctx, "/daemon.DaemonService/DeleteState", in, out, opts...)
if err != nil {
return nil, err
}
@@ -253,9 +214,8 @@ func (c *daemonServiceClient) DeleteState(ctx context.Context, in *DeleteStateRe
}
func (c *daemonServiceClient) SetNetworkMapPersistence(ctx context.Context, in *SetNetworkMapPersistenceRequest, opts ...grpc.CallOption) (*SetNetworkMapPersistenceResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(SetNetworkMapPersistenceResponse)
err := c.cc.Invoke(ctx, DaemonService_SetNetworkMapPersistence_FullMethodName, in, out, cOpts...)
err := c.cc.Invoke(ctx, "/daemon.DaemonService/SetNetworkMapPersistence", in, out, opts...)
if err != nil {
return nil, err
}
@@ -263,22 +223,20 @@ func (c *daemonServiceClient) SetNetworkMapPersistence(ctx context.Context, in *
}
func (c *daemonServiceClient) TracePacket(ctx context.Context, in *TracePacketRequest, opts ...grpc.CallOption) (*TracePacketResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(TracePacketResponse)
err := c.cc.Invoke(ctx, DaemonService_TracePacket_FullMethodName, in, out, cOpts...)
err := c.cc.Invoke(ctx, "/daemon.DaemonService/TracePacket", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *daemonServiceClient) SubscribeEvents(ctx context.Context, in *SubscribeRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[SystemEvent], error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &DaemonService_ServiceDesc.Streams[0], DaemonService_SubscribeEvents_FullMethodName, cOpts...)
func (c *daemonServiceClient) SubscribeEvents(ctx context.Context, in *SubscribeRequest, opts ...grpc.CallOption) (DaemonService_SubscribeEventsClient, error) {
stream, err := c.cc.NewStream(ctx, &DaemonService_ServiceDesc.Streams[0], "/daemon.DaemonService/SubscribeEvents", opts...)
if err != nil {
return nil, err
}
x := &grpc.GenericClientStream[SubscribeRequest, SystemEvent]{ClientStream: stream}
x := &daemonServiceSubscribeEventsClient{stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
@@ -288,13 +246,44 @@ func (c *daemonServiceClient) SubscribeEvents(ctx context.Context, in *Subscribe
return x, nil
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type DaemonService_SubscribeEventsClient = grpc.ServerStreamingClient[SystemEvent]
type DaemonService_SubscribeEventsClient interface {
Recv() (*SystemEvent, error)
grpc.ClientStream
}
type daemonServiceSubscribeEventsClient struct {
grpc.ClientStream
}
func (x *daemonServiceSubscribeEventsClient) Recv() (*SystemEvent, error) {
m := new(SystemEvent)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
func (c *daemonServiceClient) GetEvents(ctx context.Context, in *GetEventsRequest, opts ...grpc.CallOption) (*GetEventsResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(GetEventsResponse)
err := c.cc.Invoke(ctx, DaemonService_GetEvents_FullMethodName, in, out, cOpts...)
err := c.cc.Invoke(ctx, "/daemon.DaemonService/GetEvents", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *daemonServiceClient) ReloadConfig(ctx context.Context, in *ReloadConfigRequest, opts ...grpc.CallOption) (*ReloadConfigResponse, error) {
out := new(ReloadConfigResponse)
err := c.cc.Invoke(ctx, "/daemon.DaemonService/ReloadConfig", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *daemonServiceClient) SetConfigValue(ctx context.Context, in *SetConfigValueRequest, opts ...grpc.CallOption) (*SetConfigValueResponse, error) {
out := new(SetConfigValueResponse)
err := c.cc.Invoke(ctx, "/daemon.DaemonService/SetConfigValue", in, out, opts...)
if err != nil {
return nil, err
}
@@ -303,7 +292,7 @@ func (c *daemonServiceClient) GetEvents(ctx context.Context, in *GetEventsReques
// DaemonServiceServer is the server API for DaemonService service.
// All implementations must embed UnimplementedDaemonServiceServer
// for forward compatibility.
// for forward compatibility
type DaemonServiceServer interface {
// Login uses setup key to prepare configuration for the daemon.
Login(context.Context, *LoginRequest) (*LoginResponse, error)
@@ -340,17 +329,18 @@ type DaemonServiceServer interface {
// SetNetworkMapPersistence enables or disables network map persistence
SetNetworkMapPersistence(context.Context, *SetNetworkMapPersistenceRequest) (*SetNetworkMapPersistenceResponse, error)
TracePacket(context.Context, *TracePacketRequest) (*TracePacketResponse, error)
SubscribeEvents(*SubscribeRequest, grpc.ServerStreamingServer[SystemEvent]) error
SubscribeEvents(*SubscribeRequest, DaemonService_SubscribeEventsServer) error
GetEvents(context.Context, *GetEventsRequest) (*GetEventsResponse, error)
// Reloads the configuration from disk
ReloadConfig(context.Context, *ReloadConfigRequest) (*ReloadConfigResponse, error)
// Sets a configuration value (for use by regular users via the daemon)
SetConfigValue(context.Context, *SetConfigValueRequest) (*SetConfigValueResponse, error)
mustEmbedUnimplementedDaemonServiceServer()
}
// UnimplementedDaemonServiceServer must be embedded to have
// forward compatible implementations.
//
// NOTE: this should be embedded by value instead of pointer to avoid a nil
// pointer dereference when methods are called.
type UnimplementedDaemonServiceServer struct{}
// UnimplementedDaemonServiceServer must be embedded to have forward compatible implementations.
type UnimplementedDaemonServiceServer struct {
}
func (UnimplementedDaemonServiceServer) Login(context.Context, *LoginRequest) (*LoginResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Login not implemented")
@@ -406,14 +396,19 @@ func (UnimplementedDaemonServiceServer) SetNetworkMapPersistence(context.Context
func (UnimplementedDaemonServiceServer) TracePacket(context.Context, *TracePacketRequest) (*TracePacketResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method TracePacket not implemented")
}
func (UnimplementedDaemonServiceServer) SubscribeEvents(*SubscribeRequest, grpc.ServerStreamingServer[SystemEvent]) error {
func (UnimplementedDaemonServiceServer) SubscribeEvents(*SubscribeRequest, DaemonService_SubscribeEventsServer) error {
return status.Errorf(codes.Unimplemented, "method SubscribeEvents not implemented")
}
func (UnimplementedDaemonServiceServer) GetEvents(context.Context, *GetEventsRequest) (*GetEventsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetEvents not implemented")
}
func (UnimplementedDaemonServiceServer) ReloadConfig(context.Context, *ReloadConfigRequest) (*ReloadConfigResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ReloadConfig not implemented")
}
func (UnimplementedDaemonServiceServer) SetConfigValue(context.Context, *SetConfigValueRequest) (*SetConfigValueResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SetConfigValue not implemented")
}
func (UnimplementedDaemonServiceServer) mustEmbedUnimplementedDaemonServiceServer() {}
func (UnimplementedDaemonServiceServer) testEmbeddedByValue() {}
// UnsafeDaemonServiceServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to DaemonServiceServer will
@@ -423,13 +418,6 @@ type UnsafeDaemonServiceServer interface {
}
func RegisterDaemonServiceServer(s grpc.ServiceRegistrar, srv DaemonServiceServer) {
// If the following call pancis, it indicates UnimplementedDaemonServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
t.testEmbeddedByValue()
}
s.RegisterService(&DaemonService_ServiceDesc, srv)
}
@@ -443,7 +431,7 @@ func _DaemonService_Login_Handler(srv interface{}, ctx context.Context, dec func
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: DaemonService_Login_FullMethodName,
FullMethod: "/daemon.DaemonService/Login",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(DaemonServiceServer).Login(ctx, req.(*LoginRequest))
@@ -461,7 +449,7 @@ func _DaemonService_WaitSSOLogin_Handler(srv interface{}, ctx context.Context, d
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: DaemonService_WaitSSOLogin_FullMethodName,
FullMethod: "/daemon.DaemonService/WaitSSOLogin",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(DaemonServiceServer).WaitSSOLogin(ctx, req.(*WaitSSOLoginRequest))
@@ -479,7 +467,7 @@ func _DaemonService_Up_Handler(srv interface{}, ctx context.Context, dec func(in
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: DaemonService_Up_FullMethodName,
FullMethod: "/daemon.DaemonService/Up",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(DaemonServiceServer).Up(ctx, req.(*UpRequest))
@@ -497,7 +485,7 @@ func _DaemonService_Status_Handler(srv interface{}, ctx context.Context, dec fun
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: DaemonService_Status_FullMethodName,
FullMethod: "/daemon.DaemonService/Status",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(DaemonServiceServer).Status(ctx, req.(*StatusRequest))
@@ -515,7 +503,7 @@ func _DaemonService_Down_Handler(srv interface{}, ctx context.Context, dec func(
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: DaemonService_Down_FullMethodName,
FullMethod: "/daemon.DaemonService/Down",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(DaemonServiceServer).Down(ctx, req.(*DownRequest))
@@ -533,7 +521,7 @@ func _DaemonService_GetConfig_Handler(srv interface{}, ctx context.Context, dec
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: DaemonService_GetConfig_FullMethodName,
FullMethod: "/daemon.DaemonService/GetConfig",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(DaemonServiceServer).GetConfig(ctx, req.(*GetConfigRequest))
@@ -551,7 +539,7 @@ func _DaemonService_ListNetworks_Handler(srv interface{}, ctx context.Context, d
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: DaemonService_ListNetworks_FullMethodName,
FullMethod: "/daemon.DaemonService/ListNetworks",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(DaemonServiceServer).ListNetworks(ctx, req.(*ListNetworksRequest))
@@ -569,7 +557,7 @@ func _DaemonService_SelectNetworks_Handler(srv interface{}, ctx context.Context,
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: DaemonService_SelectNetworks_FullMethodName,
FullMethod: "/daemon.DaemonService/SelectNetworks",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(DaemonServiceServer).SelectNetworks(ctx, req.(*SelectNetworksRequest))
@@ -587,7 +575,7 @@ func _DaemonService_DeselectNetworks_Handler(srv interface{}, ctx context.Contex
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: DaemonService_DeselectNetworks_FullMethodName,
FullMethod: "/daemon.DaemonService/DeselectNetworks",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(DaemonServiceServer).DeselectNetworks(ctx, req.(*SelectNetworksRequest))
@@ -605,7 +593,7 @@ func _DaemonService_ForwardingRules_Handler(srv interface{}, ctx context.Context
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: DaemonService_ForwardingRules_FullMethodName,
FullMethod: "/daemon.DaemonService/ForwardingRules",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(DaemonServiceServer).ForwardingRules(ctx, req.(*EmptyRequest))
@@ -623,7 +611,7 @@ func _DaemonService_DebugBundle_Handler(srv interface{}, ctx context.Context, de
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: DaemonService_DebugBundle_FullMethodName,
FullMethod: "/daemon.DaemonService/DebugBundle",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(DaemonServiceServer).DebugBundle(ctx, req.(*DebugBundleRequest))
@@ -641,7 +629,7 @@ func _DaemonService_GetLogLevel_Handler(srv interface{}, ctx context.Context, de
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: DaemonService_GetLogLevel_FullMethodName,
FullMethod: "/daemon.DaemonService/GetLogLevel",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(DaemonServiceServer).GetLogLevel(ctx, req.(*GetLogLevelRequest))
@@ -659,7 +647,7 @@ func _DaemonService_SetLogLevel_Handler(srv interface{}, ctx context.Context, de
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: DaemonService_SetLogLevel_FullMethodName,
FullMethod: "/daemon.DaemonService/SetLogLevel",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(DaemonServiceServer).SetLogLevel(ctx, req.(*SetLogLevelRequest))
@@ -677,7 +665,7 @@ func _DaemonService_ListStates_Handler(srv interface{}, ctx context.Context, dec
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: DaemonService_ListStates_FullMethodName,
FullMethod: "/daemon.DaemonService/ListStates",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(DaemonServiceServer).ListStates(ctx, req.(*ListStatesRequest))
@@ -695,7 +683,7 @@ func _DaemonService_CleanState_Handler(srv interface{}, ctx context.Context, dec
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: DaemonService_CleanState_FullMethodName,
FullMethod: "/daemon.DaemonService/CleanState",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(DaemonServiceServer).CleanState(ctx, req.(*CleanStateRequest))
@@ -713,7 +701,7 @@ func _DaemonService_DeleteState_Handler(srv interface{}, ctx context.Context, de
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: DaemonService_DeleteState_FullMethodName,
FullMethod: "/daemon.DaemonService/DeleteState",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(DaemonServiceServer).DeleteState(ctx, req.(*DeleteStateRequest))
@@ -731,7 +719,7 @@ func _DaemonService_SetNetworkMapPersistence_Handler(srv interface{}, ctx contex
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: DaemonService_SetNetworkMapPersistence_FullMethodName,
FullMethod: "/daemon.DaemonService/SetNetworkMapPersistence",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(DaemonServiceServer).SetNetworkMapPersistence(ctx, req.(*SetNetworkMapPersistenceRequest))
@@ -749,7 +737,7 @@ func _DaemonService_TracePacket_Handler(srv interface{}, ctx context.Context, de
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: DaemonService_TracePacket_FullMethodName,
FullMethod: "/daemon.DaemonService/TracePacket",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(DaemonServiceServer).TracePacket(ctx, req.(*TracePacketRequest))
@@ -762,11 +750,21 @@ func _DaemonService_SubscribeEvents_Handler(srv interface{}, stream grpc.ServerS
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(DaemonServiceServer).SubscribeEvents(m, &grpc.GenericServerStream[SubscribeRequest, SystemEvent]{ServerStream: stream})
return srv.(DaemonServiceServer).SubscribeEvents(m, &daemonServiceSubscribeEventsServer{stream})
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type DaemonService_SubscribeEventsServer = grpc.ServerStreamingServer[SystemEvent]
type DaemonService_SubscribeEventsServer interface {
Send(*SystemEvent) error
grpc.ServerStream
}
type daemonServiceSubscribeEventsServer struct {
grpc.ServerStream
}
func (x *daemonServiceSubscribeEventsServer) Send(m *SystemEvent) error {
return x.ServerStream.SendMsg(m)
}
func _DaemonService_GetEvents_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetEventsRequest)
@@ -778,7 +776,7 @@ func _DaemonService_GetEvents_Handler(srv interface{}, ctx context.Context, dec
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: DaemonService_GetEvents_FullMethodName,
FullMethod: "/daemon.DaemonService/GetEvents",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(DaemonServiceServer).GetEvents(ctx, req.(*GetEventsRequest))
@@ -786,6 +784,42 @@ func _DaemonService_GetEvents_Handler(srv interface{}, ctx context.Context, dec
return interceptor(ctx, in, info, handler)
}
func _DaemonService_ReloadConfig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ReloadConfigRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(DaemonServiceServer).ReloadConfig(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/daemon.DaemonService/ReloadConfig",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(DaemonServiceServer).ReloadConfig(ctx, req.(*ReloadConfigRequest))
}
return interceptor(ctx, in, info, handler)
}
func _DaemonService_SetConfigValue_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SetConfigValueRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(DaemonServiceServer).SetConfigValue(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/daemon.DaemonService/SetConfigValue",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(DaemonServiceServer).SetConfigValue(ctx, req.(*SetConfigValueRequest))
}
return interceptor(ctx, in, info, handler)
}
// DaemonService_ServiceDesc is the grpc.ServiceDesc for DaemonService service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
@@ -869,6 +903,14 @@ var DaemonService_ServiceDesc = grpc.ServiceDesc{
MethodName: "GetEvents",
Handler: _DaemonService_GetEvents_Handler,
},
{
MethodName: "ReloadConfig",
Handler: _DaemonService_ReloadConfig_Handler,
},
{
MethodName: "SetConfigValue",
Handler: _DaemonService_SetConfigValue_Handler,
},
},
Streams: []grpc.StreamDesc{
{

View File

@@ -5,10 +5,13 @@ import (
"fmt"
"os"
"os/exec"
"os/signal"
"runtime"
"strconv"
"strings"
"sync"
"sync/atomic"
"syscall"
"time"
"github.com/cenkalti/backoff/v4"
@@ -95,6 +98,8 @@ func (s *Server) Start() error {
defer s.mutex.Unlock()
state := internal.CtxGetState(s.rootCtx)
s.setupReloadSignal()
if err := handlePanicLog(); err != nil {
log.Warnf("failed to redirect stderr: %v", err)
}
@@ -918,3 +923,186 @@ func sendTerminalNotification() error {
return wallCmd.Wait()
}
// Add a gRPC method to reload config from disk
func (s *Server) ReloadConfig(_ context.Context, _ *proto.ReloadConfigRequest) (*proto.ReloadConfigResponse, error) {
s.mutex.Lock()
defer s.mutex.Unlock()
config, err := internal.ReadConfig(s.latestConfigInput.ConfigPath)
if err != nil {
return nil, fmt.Errorf("failed to reload config: %v", err)
}
s.config = config
s.statusRecorder.UpdateManagementAddress(config.ManagementURL.String())
s.statusRecorder.UpdateRosenpass(config.RosenpassEnabled, config.RosenpassPermissive)
s.statusRecorder.UpdateLazyConnection(config.LazyConnectionEnabled)
log.Infof("Reloaded config from disk")
return &proto.ReloadConfigResponse{}, nil
}
// Optionally, handle SIGHUP to reload config
func (s *Server) setupReloadSignal() {
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGHUP)
go func() {
for range c {
_, err := s.ReloadConfig(context.Background(), &proto.ReloadConfigRequest{})
if err != nil {
log.Warnf("failed to reload config on SIGHUP: %v", err)
}
}
}()
}
func (s *Server) SetConfigValue(_ context.Context, req *proto.SetConfigValueRequest) (*proto.SetConfigValueResponse, error) {
s.mutex.Lock()
defer s.mutex.Unlock()
setting := req.Setting
value := req.Value
input := internal.ConfigInput{ConfigPath: s.latestConfigInput.ConfigPath}
switch setting {
case "management-url":
input.ManagementURL = value
case "admin-url":
input.AdminURL = value
case "interface-name":
input.InterfaceName = &value
case "external-ip-map":
if value == "" {
input.NATExternalIPs = []string{}
} else {
input.NATExternalIPs = strings.Split(value, ",")
}
case "extra-iface-blacklist":
if value == "" {
input.ExtraIFaceBlackList = []string{}
} else {
input.ExtraIFaceBlackList = strings.Split(value, ",")
}
case "dns-resolver-address":
input.CustomDNSAddress = []byte(value)
case "extra-dns-labels":
if value == "" {
input.DNSLabels = nil
} else {
labels := strings.Split(value, ",")
domains, err := domain.ValidateDomains(labels)
if err != nil {
return nil, fmt.Errorf("invalid DNS labels: %v", err)
}
input.DNSLabels = domains
}
case "preshared-key":
input.PreSharedKey = &value
case "enable-rosenpass":
b, err := parseBool(value)
if err != nil {
return nil, err
}
input.RosenpassEnabled = &b
case "rosenpass-permissive":
b, err := parseBool(value)
if err != nil {
return nil, err
}
input.RosenpassPermissive = &b
case "allow-server-ssh":
b, err := parseBool(value)
if err != nil {
return nil, err
}
input.ServerSSHAllowed = &b
case "network-monitor":
b, err := parseBool(value)
if err != nil {
return nil, err
}
input.NetworkMonitor = &b
case "disable-auto-connect":
b, err := parseBool(value)
if err != nil {
return nil, err
}
input.DisableAutoConnect = &b
case "disable-client-routes":
b, err := parseBool(value)
if err != nil {
return nil, err
}
input.DisableClientRoutes = &b
case "disable-server-routes":
b, err := parseBool(value)
if err != nil {
return nil, err
}
input.DisableServerRoutes = &b
case "disable-dns":
b, err := parseBool(value)
if err != nil {
return nil, err
}
input.DisableDNS = &b
case "disable-firewall":
b, err := parseBool(value)
if err != nil {
return nil, err
}
input.DisableFirewall = &b
case "block-lan-access":
b, err := parseBool(value)
if err != nil {
return nil, err
}
input.BlockLANAccess = &b
case "block-inbound":
b, err := parseBool(value)
if err != nil {
return nil, err
}
input.BlockInbound = &b
case "enable-lazy-connection":
b, err := parseBool(value)
if err != nil {
return nil, err
}
input.LazyConnectionEnabled = &b
case "wireguard-port":
var p int
_, err := fmt.Sscanf(value, "%d", &p)
if err != nil {
return nil, fmt.Errorf("invalid wireguard-port: %s", value)
}
input.WireguardPort = &p
case "dns-router-interval":
d, err := time.ParseDuration(value)
if err != nil {
return nil, fmt.Errorf("invalid duration: %v", err)
}
input.DNSRouteInterval = &d
default:
return nil, fmt.Errorf("unknown setting: %s", setting)
}
_, err := internal.UpdateOrCreateConfig(input)
if err != nil {
return nil, fmt.Errorf("failed to update config: %v", err)
}
// Reload config in memory
config, err := internal.ReadConfig(s.latestConfigInput.ConfigPath)
if err == nil {
s.config = config
}
return &proto.SetConfigValueResponse{}, nil
}
func parseBool(val string) (bool, error) {
v := strings.ToLower(val)
if v == "true" || v == "1" {
return true, nil
}
if v == "false" || v == "0" {
return false, nil
}
return false, fmt.Errorf("invalid boolean value: %s", val)
}