From 88dcf675c718202c6ca532f9ad4526845efa9df3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sven=20P=C3=BCschel?= Date: Sat, 16 Nov 2024 16:22:08 +0100 Subject: [PATCH] concentratorconfig/etcdconfigweb: add cobra arg parsing Add cobra argument parsing to concentratorconfig and etcdconfigweb to show the available configuration options to the user. Also allow changing some hardcoded values/paths using command line arguments. --- concentratorconfig/main.go | 81 ++++++++++++++++++++++---------- etcdconfigweb/main.go | 58 ++++++++++++++++------- etcdutility/cmd_showoverrides.go | 13 +++-- ffbs/etcd.go | 4 +- 4 files changed, 110 insertions(+), 46 deletions(-) diff --git a/concentratorconfig/main.go b/concentratorconfig/main.go index a2f86f3..2ff928c 100644 --- a/concentratorconfig/main.go +++ b/concentratorconfig/main.go @@ -6,8 +6,9 @@ If an error occurs, it will print it and won't update any node. Pass the simulate argument to only show the wireguard interface changes that would be applied. When it is started this way, it exits after printing the changes. -The program expects a fixed Wireguard interface name (see [WG_DEVICENAME]) and -an etcd configuration file at a fixed location (see [github.com/ffbs/etcd-tools/ffbs.CreateEtcdConnection]). +The program expects by default a Wireguard interface named "wg-nodes" +and an etcd configuration file at "/etc/etcd-client.json". +These can be changed using command line flags. */ package main @@ -24,12 +25,11 @@ import ( "github.com/ffbs/etcd-tools/ffbs" + "github.com/spf13/cobra" "golang.zx2c4.com/wireguard/wgctrl" "golang.zx2c4.com/wireguard/wgctrl/wgtypes" ) -const WG_DEVICENAME = "wg-nodes" - func sortIPNet(s []net.IPNet) { sort.Slice(s, func(i, j int) bool { res := bytes.Compare(s[i].IP[:], s[j].IP[:]) @@ -41,17 +41,12 @@ func sortIPNet(s []net.IPNet) { }) } -func calculateWGPeerUpdates(etcd *ffbs.EtcdHandler, wg *wgctrl.Client) ([]wgtypes.PeerConfig, error) { +func calculateWGPeerUpdates(etcd *ffbs.EtcdHandler, dev *wgtypes.Device) ([]wgtypes.PeerConfig, error) { nodes, defNode, err := etcd.GetAllNodeInfo(context.Background()) if err != nil { return nil, err } - dev, err := wg.Device(WG_DEVICENAME) - if err != nil { - return nil, err - } - updates := make([]wgtypes.PeerConfig, 0, 10) // remove and update existing nodes @@ -139,18 +134,15 @@ func calculateWGPeerUpdates(etcd *ffbs.EtcdHandler, wg *wgctrl.Client) ([]wgtype return updates, nil } -func main() { - simulate := false - if len(os.Args) > 1 { - switch os.Args[1] { - case "simulate": - simulate = true - default: - log.Fatalln("unknown arguments. Currently only 'simulate' is supported") - } - } +type CLIConfig struct { + EtcdConfig string + UpdateInterval time.Duration + WGDeviceName string + Simulate bool +} - etcd, err := ffbs.CreateEtcdConnection() +func run(config *CLIConfig) { + etcd, err := ffbs.CreateEtcdConnection(config.EtcdConfig) if err != nil { log.Fatalln("Couldn't setup etcd connection:", err) } @@ -163,12 +155,18 @@ func main() { for { // misusing a loop to break at any moment and still run the sleep call for { - updates, err := calculateWGPeerUpdates(etcd, wg) + dev, err := wg.Device(config.WGDeviceName) + if err != nil { + log.Println("Error getting Wireguard device", config.WGDeviceName, "and got error:", err) + break + } + + updates, err := calculateWGPeerUpdates(etcd, dev) if err != nil { log.Println("Error trying to determine the node updates:", err) break } - if simulate { + if config.Simulate { fmt.Printf("Peer updates: %v\n", updates) return } @@ -176,13 +174,46 @@ func main() { break } - if err := wg.ConfigureDevice(WG_DEVICENAME, wgtypes.Config{Peers: updates}); err != nil { + if err := wg.ConfigureDevice(config.WGDeviceName, wgtypes.Config{Peers: updates}); err != nil { log.Println("Error trying to apply the node updates:", err) break } log.Println("Updated", len(updates), "peers") break } - time.Sleep(60 * time.Second) + time.Sleep(config.UpdateInterval) + } +} + +func main() { + var config CLIConfig + + rootCmd := &cobra.Command{ + Use: "concentratorconfig", + Short: "Configure the Wireguard interface based on the etcd KV configuration", + Run: func(cmd *cobra.Command, args []string) { + run(&config) + }, + } + + rootCmd.PersistentFlags().StringVarP(&config.EtcdConfig, "etcdconfig", "e", "/etc/etcd-client.json", "Path to the etcd client configuration file") + rootCmd.MarkFlagFilename("etcdconfig", "json") + rootCmd.PersistentFlags().DurationVarP(&config.UpdateInterval, "interval", "i", 60*time.Second, "Interval to update the wireguard configuration from the etcd store") + rootCmd.PersistentFlags().StringVarP(&config.WGDeviceName, "devicename", "d", "wg-nodes", "Wireguard device name to update the configuration") + + simulateCmd := &cobra.Command{ + Use: "simulate", + Short: "Simulate updating of the wireguard devices, but don't apply them", + Run: func(cmd *cobra.Command, args []string) { + config.Simulate = true + run(&config) + }, + } + + rootCmd.AddCommand(simulateCmd) + + if err := rootCmd.Execute(); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) } } diff --git a/etcdconfigweb/main.go b/etcdconfigweb/main.go index afb893d..a891da7 100644 --- a/etcdconfigweb/main.go +++ b/etcdconfigweb/main.go @@ -1,12 +1,10 @@ /* etcdconfigweb provides an http interface to query and register nodes from the etcd KV store. -By default it will listen on port 8080 on any interface. You can change this by passing an argument -like ":1234", which would configure to listen on port 1234 on any interface or "127.0.0.1:1234" to only listen -on the IPv4 local address "127.0.0.1" on port "1234". - -It expects an etcd configuration file at a fixed location (see [github.com/ffbs/etcd-tools/ffbs.CreateEtcdConnection]) -and a signify private key to sign the requests at "/etc/ffbs/node-config.sec" +By default it will listen on port 8080 on any interface. +It expects an etcd configuration file (by default at "/etc/etcd-client.json") +and a signify private key to sign the requests (by default at "/etc/ffbs/node-config.sec"). +You can change these settings using command line options. As it doesn't need any root capabilities, it should be considered to run this executable as a normal user. @@ -17,25 +15,29 @@ The HTTP server supports two endpoints: package main import ( + "fmt" "log" "net/http" "os" "github.com/ffbs/etcd-tools/ffbs" + + "github.com/spf13/cobra" ) -func main() { - etcd, err := ffbs.CreateEtcdConnection() +type CLIConfig struct { + ListenAddr string + Key string + EtcdConfig string +} + +func run(config *CLIConfig) { + etcd, err := ffbs.CreateEtcdConnection(config.EtcdConfig) if err != nil { log.Fatalln("Couldn't setup etcd connection: ", err) } - servingAddr := ":8080" - if len(os.Args) > 1 { - servingAddr = os.Args[1] - } - - signer, err := NewSignifySignerFromPrivateKeyFile("/etc/ffbs/node-config.sec") + signer, err := NewSignifySignerFromPrivateKeyFile(config.Key) if err != nil { log.Fatalln("Couldn't parse signify private key:", err) } @@ -45,6 +47,30 @@ func main() { http.Handle("/config", &ConfigHandler{tracker: metrics, signer: signer, etcdHandler: etcd}) http.Handle("/etcd_status", metrics) - log.Println("Starting server on", servingAddr) - log.Fatal("Error running webserver:", http.ListenAndServe(servingAddr, nil)) + log.Println("Starting server on", config.ListenAddr) + log.Fatal("Error running webserver:", http.ListenAndServe(config.ListenAddr, nil)) +} + +func main() { + var config CLIConfig + + rootCmd := &cobra.Command{ + Use: "etcdconfigweb", + Short: "Provide an HTTP interface to query and register nodes from the etcd KV store", + Args: cobra.NoArgs, + Run: func(cmd *cobra.Command, args []string) { + run(&config) + }, + } + + rootCmd.PersistentFlags().StringVarP(&config.ListenAddr, "listen", "l", ":8080", "HTTP listening address to bind to") + rootCmd.PersistentFlags().StringVarP(&config.Key, "key", "k", "/etc/ffbs/node-config.sec", "Path to signify private key file to sign responses") + rootCmd.MarkFlagFilename("key", "sec") + rootCmd.PersistentFlags().StringVarP(&config.EtcdConfig, "etcdconfig", "e", "/etc/etcd-client.json", "Path to the etcd client configuration file") + rootCmd.MarkFlagFilename("etcdconfig", "json") + + if err := rootCmd.Execute(); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } } diff --git a/etcdutility/cmd_showoverrides.go b/etcdutility/cmd_showoverrides.go index 31d7808..cbc799c 100644 --- a/etcdutility/cmd_showoverrides.go +++ b/etcdutility/cmd_showoverrides.go @@ -12,17 +12,24 @@ import ( ) func init() { + var etcdConfig string + cmd := &cobra.Command{ Use: "showoverrides", Short: "Shows all Pubkeys overriding a default value", - Run: showoverrides, + Run: func(cmd *cobra.Command, args []string) { + showoverrides(etcdConfig) + }, } + cmd.PersistentFlags().StringVarP(&etcdConfig, "etcdconfig", "e", "/etc/etcd-client.json", "Path to the etcd client configuration file") + cmd.MarkFlagFilename("etcdconfig", "json") + rootCmd.AddCommand(cmd) } -func showoverrides(cmd *cobra.Command, args []string) { - etcd, err := ffbs.CreateEtcdConnection() +func showoverrides(etcdConfig string) { + etcd, err := ffbs.CreateEtcdConnection(etcdConfig) if err != nil { log.Fatalln("Couldn't setup etcd connection:", err) } diff --git a/ffbs/etcd.go b/ffbs/etcd.go index 97d6185..177a874 100644 --- a/ffbs/etcd.go +++ b/ffbs/etcd.go @@ -25,8 +25,8 @@ type EtcdConfigFile struct { // This function will only allow the configured CACert and // ignores system root certificate authorities when connecting // to the etcd server. -func CreateEtcdConnection() (*EtcdHandler, error) { - f, err := os.Open("/etc/etcd-client.json") +func CreateEtcdConnection(configFile string) (*EtcdHandler, error) { + f, err := os.Open(configFile) if err != nil { return nil, err }