Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dependency updates, configurable commands and test for range generation #1

Merged
merged 3 commits into from
Nov 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.19'
go-version: '1.22'

- uses: actions/checkout@v4

Expand All @@ -20,8 +20,9 @@ jobs:
git diff --exit-code
go vet ./...

- name: Build project
- name: Build and test project
run: |
go test ./...
mkdir bin
GOBIN="$PWD/bin" CGO_ENABLED=0 go install ./...

Expand Down
81 changes: 56 additions & 25 deletions concentratorconfig/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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[:])
Expand All @@ -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
Expand Down Expand Up @@ -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)
}
Expand All @@ -163,26 +155,65 @@ 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
}
if len(updates) == 0 {
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)
}
}
50 changes: 26 additions & 24 deletions etcdconfigweb/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,31 @@ var MISSING_V6MTU = errors.New("Missing v6mtu query parameter")
var MISSING_PUBKEY = errors.New("Missing pubkey query parameter")
var MISSING_NONCE = errors.New("Missing nonce query parameter")

func generateNodeAddressesAndRanges(info *ffbs.NodeInfo) {
const V4_BASE uint32 = 10 << 24
const V4_RANGE_SIZE uint8 = 10
const V6_BASE_HIGH uint64 = 0x20010bf70381 << 16

num := *info.ID

var v4Addr [net.IPv4len]byte
binary.BigEndian.PutUint32(v4Addr[:], V4_BASE|(uint32(num)<<V4_RANGE_SIZE))
v4range := fmt.Sprintf("%s/%d", net.IP(v4Addr[:]), 8*net.IPv4len-V4_RANGE_SIZE)
v4Addr[net.IPv4len-1] = 1
v4addr := net.IP(v4Addr[:]).String()

var v6Addr [net.IPv6len]byte
binary.BigEndian.PutUint64(v6Addr[:8], V6_BASE_HIGH|uint64(num))
v6range := fmt.Sprintf("%s/64", net.IP(v6Addr[:]))
v6Addr[net.IPv6len-1] = 1
v6addr := net.IP(v6Addr[:]).String()

info.Address4 = &v4addr
info.Range4 = &v4range
info.Address6 = &v6addr
info.Range6 = &v6range
}

func (ch ConfigHandler) handleRequest(ctx context.Context, query url.Values, headers http.Header) (*ConfigResponse, error) {
var v6mtu uint64
var err error
Expand Down Expand Up @@ -80,30 +105,7 @@ func (ch ConfigHandler) handleRequest(ctx context.Context, query url.Values, hea
}

// insert new node
err := ch.etcdHandler.CreateNode(ctx, pubkey, func(info *ffbs.NodeInfo) {
const V4_BASE uint32 = 10 << 24
const V4_RANGE_SIZE uint8 = 10
const V6_BASE_HIGH uint64 = 0x20010bf70381 << 16

num := *info.ID

var v4Addr [net.IPv4len]byte
binary.BigEndian.PutUint32(v4Addr[:], V4_BASE|(uint32(num)<<V4_RANGE_SIZE))
v4range := fmt.Sprintf("%s/%d", net.IP(v4Addr[:]), 8*net.IPv4len-V4_RANGE_SIZE)
v4Addr[net.IPv4len-1] = 1
v4addr := net.IP(v4Addr[:]).String()

var v6Addr [net.IPv6len]byte
binary.BigEndian.PutUint64(v6Addr[:8], V6_BASE_HIGH|uint64(num))
v6range := fmt.Sprintf("%s/64", net.IP(v6Addr[:]))
v6Addr[net.IPv6len-1] = 1
v6addr := net.IP(v6Addr[:]).String()

info.Address4 = &v4addr
info.Range4 = &v4range
info.Address6 = &v6addr
info.Range6 = &v6range
})
err := ch.etcdHandler.CreateNode(ctx, pubkey, generateNodeAddressesAndRanges)
if err != nil {
return nil, err
}
Expand Down
31 changes: 31 additions & 0 deletions etcdconfigweb/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package main

import (
"testing"

"github.com/ffbs/etcd-tools/ffbs"

"github.com/stretchr/testify/assert"
)

func TestGenerateAddressesAndRanges(t *testing.T) {
// Using https://freifunk-bs.de/map/#!/de/map/f4068dcf9fe1 as an example
id := uint64(30)
node := ffbs.NodeInfo{ID: &id}
generateNodeAddressesAndRanges(&node)
assert.Equal(t, "10.0.120.1", *node.Address4)
assert.Equal(t, "10.0.120.0/22", *node.Range4)
assert.Equal(t, "2001:bf7:381:1e::1", *node.Address6)
assert.Equal(t, "2001:bf7:381:1e::/64", *node.Range6)
}

func TestGenerateHighestAddressesAndRanges(t *testing.T) {
// IPv4 base range is /8. Range of every subnet is /22 => 22-8 = 14 bits of freedom
id := uint64(16383)
node := ffbs.NodeInfo{ID: &id}
generateNodeAddressesAndRanges(&node)
assert.Equal(t, "10.255.252.1", *node.Address4)
assert.Equal(t, "10.255.252.0/22", *node.Range4)
assert.Equal(t, "2001:bf7:381:3fff::1", *node.Address6)
assert.Equal(t, "2001:bf7:381:3fff::/64", *node.Range6)
}
58 changes: 42 additions & 16 deletions etcdconfigweb/main.go
Original file line number Diff line number Diff line change
@@ -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.

Expand All @@ -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)
}
Expand All @@ -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)
}
}
13 changes: 10 additions & 3 deletions etcdutility/cmd_showoverrides.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
4 changes: 2 additions & 2 deletions ffbs/etcd.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
Loading
Loading