228 lines
5.3 KiB
Go
228 lines
5.3 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"net"
|
|
"net/http"
|
|
"os"
|
|
"os/exec"
|
|
"os/signal"
|
|
"path"
|
|
"path/filepath"
|
|
"strings"
|
|
"text/template"
|
|
|
|
"github.com/Xe/waifud/key2hex"
|
|
"github.com/facebookgo/flagenv"
|
|
"github.com/go-redis/redis/v8"
|
|
"golang.zx2c4.com/wireguard/conn"
|
|
"golang.zx2c4.com/wireguard/device"
|
|
"golang.zx2c4.com/wireguard/tun/netstack"
|
|
"within.website/ln"
|
|
"within.website/ln/opname"
|
|
)
|
|
|
|
var (
|
|
redisURL = flag.String("redis-url", "redis://chrysalis", "the url to dial out to Redis")
|
|
wgPort = flag.Int("wireguard-host-port", 28139, "what port to have the kernel listen on wireguard")
|
|
wgHostAddr = flag.String("wireguard-host-addr", "169.254.169.253/30", "what IP range to have for the metadata service")
|
|
wgGuestAddr = flag.String("wireguard-guest-addr", "169.254.169.254", "the IP address for the metadata service")
|
|
wgInterfaceName = flag.String("wireguard-interface-name", "waifud-metadata", "the wireguard interface for the metadata service")
|
|
wgHostPrivateKey = flag.String("wireguard-host-private-key", "./var/waifud-host.privkey", "wireguard host private key path (b64)")
|
|
wgHostPubkey = flag.String("wireguard-host-public-key", "./var/waifud-host.pubkey", "wireguard host public key path (b64)")
|
|
wgGuestPrivateKey = flag.String("wireguard-guest-private-key", "./var/waifud-guest.privkey", "wireguard guest private key path (b64)")
|
|
wgGuestPubkey = flag.String("wireguard-guest-public-key", "./var/waifud-guest.pubkey", "wireguard guest public key path (b64)")
|
|
)
|
|
|
|
func main() {
|
|
flagenv.Parse()
|
|
flag.Parse()
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
ctx = opname.With(ctx, "main")
|
|
|
|
rOptions, err := redis.ParseURL(*redisURL)
|
|
if err != nil {
|
|
ln.FatalErr(ctx, err, ln.Action("parsing redis url"))
|
|
}
|
|
|
|
rdb := redis.NewClient(rOptions)
|
|
defer rdb.Close()
|
|
|
|
c := make(chan os.Signal, 1)
|
|
signal.Notify(c, os.Interrupt)
|
|
defer func() {
|
|
signal.Stop(c)
|
|
cancel()
|
|
}()
|
|
|
|
ms := metadataServer{cli: rdb}
|
|
go ms.Run(ctx)
|
|
|
|
select {
|
|
case <-c:
|
|
cancel()
|
|
case <-ctx.Done():
|
|
}
|
|
}
|
|
|
|
func run(args ...string) error {
|
|
log.Println("running command:", strings.Join(args, " "))
|
|
cmd := exec.Command(args[0], args[1:]...)
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
return cmd.Run()
|
|
}
|
|
|
|
type metadataServer struct {
|
|
cli *redis.Client
|
|
}
|
|
|
|
func (ms metadataServer) Run(ctx context.Context) {
|
|
err := ms.listen(ctx)
|
|
if err != nil {
|
|
ln.FatalErr(ctx, err)
|
|
}
|
|
}
|
|
|
|
func (ms metadataServer) listen(ctx context.Context) error {
|
|
err := establishMetadataInterface()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
err = loadUAPI(&buf)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
tun, tnet, err := netstack.CreateNetTUN([]net.IP{net.ParseIP(*wgGuestAddr)}, []net.IP{}, 1420)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
dev := device.NewDevice(tun, conn.NewDefaultBind(), device.NewLogger(4, ""))
|
|
dev.IpcSet(buf.String())
|
|
dev.Up()
|
|
|
|
lis, err := tnet.ListenTCP(&net.TCPAddr{Port: 80})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
go func() {
|
|
<-ctx.Done()
|
|
dev.Down()
|
|
lis.Close()
|
|
}()
|
|
|
|
mux := http.NewServeMux()
|
|
mux.Handle("/instance/", ms)
|
|
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
|
io.WriteString(w, "waifud metadata service - https://github.com/Xe/waifud")
|
|
})
|
|
|
|
srv := &http.Server{
|
|
Handler: mux,
|
|
}
|
|
|
|
ln.FatalErr(ctx, srv.Serve(lis))
|
|
|
|
return nil
|
|
}
|
|
|
|
func establishMetadataInterface() error {
|
|
run("modprobe", "wireguard")
|
|
run("ip", "link", "del", "dev", *wgInterfaceName)
|
|
err := run("ip", "link", "add", "dev", *wgInterfaceName, "type", "wireguard")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = run("ip", "address", "add", *wgHostAddr, "dev", *wgInterfaceName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = run("wg", "set", *wgInterfaceName, "private-key", *wgHostPrivateKey, "listen-port", fmt.Sprint(*wgPort))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = run("ip", "link", "set", "up", "dev", *wgInterfaceName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
guestPubKey, err := os.ReadFile(*wgGuestPubkey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = run("wg", "set", *wgInterfaceName, "peer", strings.TrimSpace(string(guestPubKey)), "persistent-keepalive", "25", "allowed-ips", *wgGuestAddr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = run("ip", "route", "replace", *wgGuestAddr+"/32", "dev", *wgInterfaceName, "table", "main")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func loadUAPI(w io.Writer) error {
|
|
hostPubkey, err := key2hex.FromFile(*wgHostPubkey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
guestPrivateKey, err := key2hex.FromFile(*wgGuestPrivateKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
templ := template.Must(template.New("wg-quick").Parse(uAPITemplate))
|
|
return templ.Execute(w, struct {
|
|
Privkey string
|
|
Pubkey string
|
|
HostPort int
|
|
HostAddr string
|
|
}{
|
|
Privkey: guestPrivateKey,
|
|
Pubkey: hostPubkey,
|
|
HostPort: *wgPort,
|
|
HostAddr: *wgHostAddr,
|
|
})
|
|
}
|
|
|
|
const uAPITemplate = `private_key={{.Privkey}}
|
|
listen_port=0
|
|
public_key={{.Pubkey}}
|
|
endpoint=192.168.122.1:{{.HostPort}}
|
|
allowed_ip={{.HostAddr}}
|
|
persistent_keepalive_interval=5
|
|
`
|
|
|
|
func (ms metadataServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
dir, file := path.Split(r.URL.Path)
|
|
vmID := filepath.Base(dir)
|
|
|
|
data, err := ms.cli.Get(r.Context(), vmID+"/"+file).Result()
|
|
if err != nil {
|
|
ln.Error(r.Context(), err, ln.F{"vmID": vmID, "file": file})
|
|
http.Error(w, "not found", http.StatusNotFound)
|
|
}
|
|
|
|
fmt.Fprintln(w, data)
|
|
}
|