wg-manager: new pkg
This commit is contained in:
parent
e9ec1eb9b9
commit
b3b2e5f11e
23
pkg/wg-manager/Dockerfile
Normal file
23
pkg/wg-manager/Dockerfile
Normal file
@ -0,0 +1,23 @@
|
||||
FROM golang:alpine as gobuild
|
||||
|
||||
RUN apk add --no-cache git
|
||||
|
||||
WORKDIR /go/src/wg-manager
|
||||
|
||||
ADD cmd ./
|
||||
|
||||
RUN go get -d -v
|
||||
RUN go build -v
|
||||
|
||||
|
||||
FROM alpine
|
||||
MAINTAINER Pierre-Olivier Mercier <nemunaire@nemunai.re>
|
||||
|
||||
RUN apk add --no-cache --initdb \
|
||||
wireguard-tools-wg
|
||||
|
||||
EXPOSE 8081
|
||||
|
||||
COPY --from=gobuild /go/src/wg-manager/wg-manager /bin/wg-manager
|
||||
|
||||
ENTRYPOINT ["/bin/wg-manager"]
|
2
pkg/wg-manager/build.yml
Normal file
2
pkg/wg-manager/build.yml
Normal file
@ -0,0 +1,2 @@
|
||||
image: wg-manager
|
||||
network: true
|
44
pkg/wg-manager/cmd/main.go
Normal file
44
pkg/wg-manager/cmd/main.go
Normal file
@ -0,0 +1,44 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var bind = flag.String("bind", ":8081", "Bind port/socket")
|
||||
flag.Parse()
|
||||
|
||||
// Prepare graceful shutdown
|
||||
interrupt := make(chan os.Signal, 1)
|
||||
signal.Notify(interrupt, os.Interrupt, syscall.SIGTERM)
|
||||
signal.Notify(interrupt, os.Interrupt, syscall.SIGINT)
|
||||
|
||||
srv := &http.Server{
|
||||
Addr: *bind,
|
||||
}
|
||||
|
||||
log.Println("Registering handlers...")
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/register", register)
|
||||
http.HandleFunc("/", mux.ServeHTTP)
|
||||
|
||||
// Serve content
|
||||
go func() {
|
||||
log.Fatal(srv.ListenAndServe())
|
||||
}()
|
||||
log.Println(fmt.Sprintf("Ready, listening on %s", *bind))
|
||||
|
||||
// Wait shutdown signal
|
||||
<-interrupt
|
||||
|
||||
log.Print("The service is shutting down...")
|
||||
srv.Shutdown(context.Background())
|
||||
log.Println("done")
|
||||
}
|
141
pkg/wg-manager/cmd/register.go
Normal file
141
pkg/wg-manager/cmd/register.go
Normal file
@ -0,0 +1,141 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
IFaceName = "wg0"
|
||||
TunnelPort = 12912
|
||||
)
|
||||
|
||||
var (
|
||||
SrvPubKey string
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Generate private key
|
||||
outPrvK, err := exec.Command("wg", "genkey").Output()
|
||||
if err != nil {
|
||||
log.Fatal("Unable to generate prvkey:", err)
|
||||
return
|
||||
}
|
||||
outPrvK = bytes.TrimSpace(outPrvK)
|
||||
|
||||
prvFile, err := ioutil.TempFile("", "wg")
|
||||
if err != nil {
|
||||
log.Fatal("Unable to create tmpPrvFile", err)
|
||||
return
|
||||
}
|
||||
defer os.Remove(prvFile.Name())
|
||||
|
||||
if _, err := prvFile.Write(outPrvK); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err := prvFile.Close(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
|
||||
// Calculate public key
|
||||
cmdPubK := exec.Command("wg", "pubkey")
|
||||
cmdPubK.Stdin = bytes.NewReader(outPrvK)
|
||||
outPubK, err := cmdPubK.Output()
|
||||
if err != nil {
|
||||
log.Fatal("Unable to calculate pubkey:", err)
|
||||
return
|
||||
}
|
||||
SrvPubKey = string(bytes.TrimSpace(outPubK))
|
||||
|
||||
// Set parameters
|
||||
outSet, err := exec.Command("wg", "set", IFaceName, "private-key", prvFile.Name(), "listen-port", strconv.Itoa(TunnelPort)).CombinedOutput()
|
||||
if err != nil {
|
||||
log.Fatal("Something happend during wg set:", err, string(outSet))
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("wg configured: public key:%s", string(outPubK))
|
||||
}
|
||||
|
||||
type PubTunnel struct {
|
||||
PubKey []byte
|
||||
}
|
||||
|
||||
func register(w http.ResponseWriter, r *http.Request) {
|
||||
if addr := r.Header.Get("X-Forwarded-For"); addr != "" {
|
||||
r.RemoteAddr = addr
|
||||
}
|
||||
log.Printf("%s \"%s %s\" [%s]\n", r.RemoteAddr, r.Method, r.URL.Path, r.UserAgent())
|
||||
|
||||
// Read the body
|
||||
if r.ContentLength < 0 || r.ContentLength > 6553600 {
|
||||
http.Error(w, "{errmsg:\"Request too large or request size unknown\"}", http.StatusRequestEntityTooLarge)
|
||||
return
|
||||
}
|
||||
|
||||
var pt PubTunnel
|
||||
if err := json.NewDecoder(r.Body).Decode(&pt); err != nil {
|
||||
http.Error(w, fmt.Sprintf("{errmsg:%q}", err), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if next_ip, err := findNextIP(); err != nil {
|
||||
http.Error(w, fmt.Sprintf("{errmsg:%q}", err), http.StatusBadRequest)
|
||||
return
|
||||
} else {
|
||||
addWgPeer(pt.PubKey, next_ip)
|
||||
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
w.Write([]byte(fmt.Sprintf(`# Address=%s/18
|
||||
|
||||
[Peer]
|
||||
PublicKey = %s
|
||||
Endpoint = %s:%d
|
||||
AllowedIPs = 0.0.0.0/0
|
||||
PersistentKeepalive = 15
|
||||
`, next_ip, SrvPubKey, "172.17.0.15", TunnelPort)))
|
||||
}
|
||||
}
|
||||
|
||||
func findNextIP() (next_ip net.IP, err error) {
|
||||
var out []byte
|
||||
out, err = exec.Command("wg", "show", IFaceName, "allowed-ips").Output()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
all_ips := string(out)
|
||||
|
||||
for j := byte(128); j < 190; j++ {
|
||||
for i := byte(1); i < 254; i++ {
|
||||
if !strings.Contains(all_ips, fmt.Sprintf("172.23.%d.%d/32", j, i)) {
|
||||
next_ip = net.IPv4(172, 23, j, i)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = errors.New("No IP found")
|
||||
return
|
||||
}
|
||||
|
||||
func addWgPeer(cltPubKey []byte, next_ip net.IP) (err error) {
|
||||
_, err = exec.Command(
|
||||
"wg", "set", IFaceName,
|
||||
"peer", base64.StdEncoding.EncodeToString(cltPubKey),
|
||||
"allowed-ips", fmt.Sprintf("%s/32", next_ip),
|
||||
).Output()
|
||||
return
|
||||
}
|
Reference in New Issue
Block a user