Initial working client
parent
93073c4d8e
commit
841f3a16e3
128
cmd/request.go
128
cmd/request.go
|
@ -1,12 +1,18 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
"github.com/serverwentdown/wireguard-negotiator/lib"
|
"github.com/serverwentdown/wireguard-negotiator/lib"
|
||||||
"github.com/urfave/cli/v2"
|
"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{
|
var CmdRequest = &cli.Command{
|
||||||
Name: "request",
|
Name: "request",
|
||||||
Usage: "Set up local WireGuard",
|
Usage: "Set up local WireGuard",
|
||||||
|
@ -29,8 +35,8 @@ var CmdRequest = &cli.Command{
|
||||||
Name: "networkd",
|
Name: "networkd",
|
||||||
Aliases: []string{"n"},
|
Aliases: []string{"n"},
|
||||||
Value: "",
|
Value: "",
|
||||||
DefaultText: "/etc/systemd/network/<interface>.netdev",
|
DefaultText: "/etc/systemd/network/<interface>",
|
||||||
Usage: "Path to save a networkd configuration file",
|
Usage: "Path to save networkd configuration. Appends .netdev and .network extensions",
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "type",
|
Name: "type",
|
||||||
|
@ -56,22 +62,118 @@ var CmdRequest = &cli.Command{
|
||||||
func runRequest(ctx *cli.Context) error {
|
func runRequest(ctx *cli.Context) error {
|
||||||
inter := ctx.String("interface")
|
inter := ctx.String("interface")
|
||||||
netBackend := ctx.String("type")
|
netBackend := ctx.String("type")
|
||||||
noneConfig := ctx.String("none")
|
/*
|
||||||
if !ctx.IsSet("none") {
|
noneConfig := ctx.String("none")
|
||||||
noneConfig = "/etc/wireguard/" + inter + ".conf"
|
if !ctx.IsSet("none") {
|
||||||
}
|
noneConfig = "/etc/wireguard/" + inter + ".conf"
|
||||||
|
}
|
||||||
|
*/
|
||||||
networkdConfig := ctx.String("networkd")
|
networkdConfig := ctx.String("networkd")
|
||||||
if !ctx.IsSet("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"))
|
client := lib.NewClient(ctx.String("server"), ctx.Bool("insecure"))
|
||||||
|
|
||||||
log.Println(inter)
|
// Generate the private key and public key
|
||||||
log.Println(netBackend)
|
privateKey, err := wgtypes.GeneratePrivateKey()
|
||||||
log.Println(noneConfig)
|
if err != nil {
|
||||||
log.Println(networkdConfig)
|
return err
|
||||||
log.Println(client)
|
}
|
||||||
|
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
|
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"
|
"os/signal"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/serverwentdown/wireguard-negotiator/lib"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
"gopkg.in/ini.v1"
|
"gopkg.in/ini.v1"
|
||||||
|
@ -140,13 +141,7 @@ func runServer(ctx *cli.Context) error {
|
||||||
IP: interfIPNet.IP.Mask(interfIPNet.Mask),
|
IP: interfIPNet.IP.Mask(interfIPNet.Mask),
|
||||||
Mask: interfIPNet.Mask,
|
Mask: interfIPNet.Mask,
|
||||||
}
|
}
|
||||||
resp := struct {
|
resp := lib.PeerConfigResponse{
|
||||||
InterfaceIPs []string
|
|
||||||
AllowedIPs []string
|
|
||||||
PublicKey string
|
|
||||||
Endpoint string
|
|
||||||
PersistentKeepaliveInterval int
|
|
||||||
}{
|
|
||||||
[]string{ipNet.String()},
|
[]string{ipNet.String()},
|
||||||
[]string{netIPNet.String()},
|
[]string{netIPNet.String()},
|
||||||
serverPublicKey,
|
serverPublicKey,
|
||||||
|
@ -219,16 +214,16 @@ func gater(queue chan request, result chan request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func configAddPeer(config string, req request) error {
|
func configAddPeer(config string, req request) error {
|
||||||
// For every request, we'll just open the config file again and rewrite it
|
// For every request, open the config file again and rewrite it. Acceptable
|
||||||
// We don't need to optimise this because it happens infrequently
|
// 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()
|
cfg := ini.Empty()
|
||||||
sec, _ := cfg.NewSection("Peer")
|
sec, _ := cfg.NewSection("Peer")
|
||||||
publicKey := sec.Key("PublicKey")
|
publicKey := sec.Key("PublicKey")
|
||||||
// TODO: Do we need validation?
|
// TODO: Validation is needed
|
||||||
publicKey.SetValue(req.publicKey)
|
publicKey.SetValue(req.publicKey)
|
||||||
allowedIPs := sec.Key("AllowedIPs")
|
allowedIPs := sec.Key("AllowedIPs")
|
||||||
allowedHost := ipToIPNetWithHostMask(req.ip)
|
allowedHost := ipToIPNetWithHostMask(req.ip)
|
||||||
|
@ -261,9 +256,9 @@ func configReadInterfacePublicKey(config string) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func interAddPeer(inter string, req request, config 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)
|
cmd := exec.Command("wg", "setconf", inter, config)
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -272,8 +267,6 @@ func interAddPeer(inter string, req request, config string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helpers
|
|
||||||
|
|
||||||
func ipToIPNetWithHostMask(ip net.IP) net.IPNet {
|
func ipToIPNetWithHostMask(ip net.IP) net.IPNet {
|
||||||
if ip4 := ip.To4(); ip4 != nil {
|
if ip4 := ip.To4(); ip4 != nil {
|
||||||
return net.IPNet{
|
return net.IPNet{
|
||||||
|
|
|
@ -2,11 +2,16 @@ package lib
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var ErrRequestFailed = fmt.Errorf("request for peer config was not successful")
|
||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
ServerURL string
|
serverURL string
|
||||||
httpClient *http.Client
|
httpClient *http.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,12 +23,31 @@ func NewClient(serverURL string, insecure bool) *Client {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return &Client{
|
return &Client{
|
||||||
ServerURL: serverURL,
|
serverURL: serverURL,
|
||||||
// We don't need to set a connection timeout
|
// Client is not used in time or resource sensitive environments, therefore
|
||||||
|
// omitting timeout reduces code
|
||||||
httpClient: &http.Client{},
|
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