Initial working client
parent
93073c4d8e
commit
841f3a16e3
128
cmd/request.go
128
cmd/request.go
|
@ -1,12 +1,18 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"log"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"text/template"
|
||||
|
||||
"github.com/serverwentdown/wireguard-negotiator/lib"
|
||||
"github.com/urfave/cli/v2"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
)
|
||||
|
||||
var ErrTypeNotValid = fmt.Errorf("network interface backend type not valid")
|
||||
|
||||
var CmdRequest = &cli.Command{
|
||||
Name: "request",
|
||||
Usage: "Set up local WireGuard",
|
||||
|
@ -29,8 +35,8 @@ var CmdRequest = &cli.Command{
|
|||
Name: "networkd",
|
||||
Aliases: []string{"n"},
|
||||
Value: "",
|
||||
DefaultText: "/etc/systemd/network/<interface>.netdev",
|
||||
Usage: "Path to save a networkd configuration file",
|
||||
DefaultText: "/etc/systemd/network/<interface>",
|
||||
Usage: "Path to save networkd configuration. Appends .netdev and .network extensions",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "type",
|
||||
|
@ -56,22 +62,118 @@ var CmdRequest = &cli.Command{
|
|||
func runRequest(ctx *cli.Context) error {
|
||||
inter := ctx.String("interface")
|
||||
netBackend := ctx.String("type")
|
||||
noneConfig := ctx.String("none")
|
||||
if !ctx.IsSet("none") {
|
||||
noneConfig = "/etc/wireguard/" + inter + ".conf"
|
||||
}
|
||||
/*
|
||||
noneConfig := ctx.String("none")
|
||||
if !ctx.IsSet("none") {
|
||||
noneConfig = "/etc/wireguard/" + inter + ".conf"
|
||||
}
|
||||
*/
|
||||
networkdConfig := ctx.String("networkd")
|
||||
if !ctx.IsSet("networkd") {
|
||||
networkdConfig = "/etc/systemd/network/" + inter + ".netdev"
|
||||
networkdConfig = "/etc/systemd/network/" + inter
|
||||
}
|
||||
|
||||
client := lib.NewClient(ctx.String("server"), ctx.Bool("insecure"))
|
||||
|
||||
log.Println(inter)
|
||||
log.Println(netBackend)
|
||||
log.Println(noneConfig)
|
||||
log.Println(networkdConfig)
|
||||
log.Println(client)
|
||||
// Generate the private key and public key
|
||||
privateKey, err := wgtypes.GeneratePrivateKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
publicKey := privateKey.PublicKey()
|
||||
|
||||
// Ensure that given files can be opened
|
||||
var netdevFile, networkFile *os.File
|
||||
switch netBackend {
|
||||
case "networkd":
|
||||
netdevFile, err = os.OpenFile(networkdConfig+".netdev", os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("opening %s failed: %w", networkdConfig+".netdev", err)
|
||||
}
|
||||
networkFile, err = os.OpenFile(networkdConfig+".network", os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("opening %s failed: %w", networkdConfig+".network", err)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("%w: %s", ErrTypeNotValid, netBackend)
|
||||
}
|
||||
|
||||
// Perform the request
|
||||
peerConfigResponse, err := client.Request(publicKey.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config := interfaceAndPeerConfig{
|
||||
peerConfigResponse,
|
||||
privateKey.String(),
|
||||
inter,
|
||||
}
|
||||
|
||||
// Generate configuration
|
||||
switch netBackend {
|
||||
case "networkd":
|
||||
err = configureNetworkd(config, netdevFile, networkFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("%w: %s", ErrTypeNotValid, netBackend)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type interfaceAndPeerConfig struct {
|
||||
lib.PeerConfigResponse
|
||||
PrivateKey string
|
||||
InterfaceName string
|
||||
}
|
||||
|
||||
const networkdNetdevTemplate = `
|
||||
[NetDev]
|
||||
Name = {{.InterfaceName}}
|
||||
Kind = wireguard
|
||||
Description = WireGuard {{.InterfaceName}} generated with wireguard-negotiator
|
||||
|
||||
[WireGuard]
|
||||
PrivateKey = {{.PrivateKey}}
|
||||
|
||||
[WireGuardPeer]
|
||||
PublicKey = {{.PublicKey}}
|
||||
AllowedIPs = {{range $i, $a := .AllowedIPs}}{{if gt $i 0}}, {{end}}{{.}}{{end}}
|
||||
Endpoint = {{.Endpoint}}
|
||||
PersistentKeepalive = {{.PersistentKeepalive}}
|
||||
`
|
||||
|
||||
const networkdNetworkTemplate = `
|
||||
[Match]
|
||||
Name = {{.InterfaceName}}
|
||||
|
||||
[Network]
|
||||
{{range $i, $a := .InterfaceIPs}}
|
||||
Address = {{.}}
|
||||
{{end}}
|
||||
`
|
||||
|
||||
func configureNetworkd(config interfaceAndPeerConfig, netdevFile *os.File, networkFile *os.File) error {
|
||||
// For ease of maintenance, just render a textual template
|
||||
netdevTemplate := template.Must(template.New("networkd-netdev").Parse(networkdNetdevTemplate))
|
||||
networkTemplate := template.Must(template.New("networkd-network").Parse(networkdNetworkTemplate))
|
||||
|
||||
err := netdevTemplate.Execute(netdevFile, config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("netdev template: %w", err)
|
||||
}
|
||||
err = networkTemplate.Execute(networkFile, config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("network template: %w", err)
|
||||
}
|
||||
|
||||
// For now, simply run one fixed command to reread from the config file
|
||||
cmd := exec.Command("systemctl", "restart", "systemd-networkd")
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return fmt.Errorf("systemctl restart systemd-networkd failed: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"github.com/serverwentdown/wireguard-negotiator/lib"
|
||||
"github.com/urfave/cli/v2"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
"gopkg.in/ini.v1"
|
||||
|
@ -140,13 +141,7 @@ func runServer(ctx *cli.Context) error {
|
|||
IP: interfIPNet.IP.Mask(interfIPNet.Mask),
|
||||
Mask: interfIPNet.Mask,
|
||||
}
|
||||
resp := struct {
|
||||
InterfaceIPs []string
|
||||
AllowedIPs []string
|
||||
PublicKey string
|
||||
Endpoint string
|
||||
PersistentKeepaliveInterval int
|
||||
}{
|
||||
resp := lib.PeerConfigResponse{
|
||||
[]string{ipNet.String()},
|
||||
[]string{netIPNet.String()},
|
||||
serverPublicKey,
|
||||
|
@ -219,16 +214,16 @@ func gater(queue chan request, result chan request) {
|
|||
}
|
||||
|
||||
func configAddPeer(config string, req request) error {
|
||||
// For every request, we'll just open the config file again and rewrite it
|
||||
// We don't need to optimise this because it happens infrequently
|
||||
// For every request, open the config file again and rewrite it. Acceptable
|
||||
// because this happens infrequently
|
||||
|
||||
// Preferably in the future, we treat the configuration as a database
|
||||
// Preferably in the future, treat the configuration as a database
|
||||
|
||||
// For now, we append to the config file
|
||||
// For now, append to the config file
|
||||
cfg := ini.Empty()
|
||||
sec, _ := cfg.NewSection("Peer")
|
||||
publicKey := sec.Key("PublicKey")
|
||||
// TODO: Do we need validation?
|
||||
// TODO: Validation is needed
|
||||
publicKey.SetValue(req.publicKey)
|
||||
allowedIPs := sec.Key("AllowedIPs")
|
||||
allowedHost := ipToIPNetWithHostMask(req.ip)
|
||||
|
@ -261,9 +256,9 @@ func configReadInterfacePublicKey(config string) (string, error) {
|
|||
}
|
||||
|
||||
func interAddPeer(inter string, req request, config string) error {
|
||||
// For every request, we also need to dynamically add the peer to the interface
|
||||
// For every request, dynamically add the peer to the interface
|
||||
|
||||
// For now, we simply run one fixed command to reread from the config file
|
||||
// For now, simply run one fixed command to reread from the config file
|
||||
cmd := exec.Command("wg", "setconf", inter, config)
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
|
@ -272,8 +267,6 @@ func interAddPeer(inter string, req request, config string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Helpers
|
||||
|
||||
func ipToIPNetWithHostMask(ip net.IP) net.IPNet {
|
||||
if ip4 := ip.To4(); ip4 != nil {
|
||||
return net.IPNet{
|
||||
|
|
|
@ -2,11 +2,16 @@ package lib
|
|||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
var ErrRequestFailed = fmt.Errorf("request for peer config was not successful")
|
||||
|
||||
type Client struct {
|
||||
ServerURL string
|
||||
serverURL string
|
||||
httpClient *http.Client
|
||||
}
|
||||
|
||||
|
@ -18,12 +23,31 @@ func NewClient(serverURL string, insecure bool) *Client {
|
|||
}
|
||||
}
|
||||
return &Client{
|
||||
ServerURL: serverURL,
|
||||
// We don't need to set a connection timeout
|
||||
serverURL: serverURL,
|
||||
// Client is not used in time or resource sensitive environments, therefore
|
||||
// omitting timeout reduces code
|
||||
httpClient: &http.Client{},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) Create() {
|
||||
func (c *Client) Request(publicKey string) (PeerConfigResponse, error) {
|
||||
peerConfigRequest := url.Values{}
|
||||
peerConfigRequest.Set("PublicKey", publicKey)
|
||||
|
||||
resp, err := c.httpClient.PostForm(c.serverURL+"/request", peerConfigRequest)
|
||||
if err != nil {
|
||||
return PeerConfigResponse{}, fmt.Errorf("unable to request: %w", err)
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return PeerConfigResponse{}, ErrRequestFailed
|
||||
}
|
||||
decoder := json.NewDecoder(resp.Body)
|
||||
|
||||
var peerConfigResponse PeerConfigResponse
|
||||
err = decoder.Decode(&peerConfigResponse)
|
||||
if err != nil {
|
||||
return PeerConfigResponse{}, fmt.Errorf("unable to request: %w", err)
|
||||
}
|
||||
|
||||
return peerConfigResponse, nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
package lib
|
||||
|
||||
type PeerConfigResponse struct {
|
||||
InterfaceIPs []string
|
||||
AllowedIPs []string
|
||||
PublicKey string
|
||||
Endpoint string
|
||||
PersistentKeepalive int
|
||||
}
|
Loading…
Reference in New Issue