diff --git a/pkg/configmanager/handlers.go b/pkg/configmanager/handlers.go index a63674d..05f7a4f 100644 --- a/pkg/configmanager/handlers.go +++ b/pkg/configmanager/handlers.go @@ -77,6 +77,21 @@ func (c *ConfigManager) refreshClients(w http.ResponseWriter, r *http.Request) { returnError(w, fmt.Errorf("method not supported"), http.StatusBadRequest) } } +func (c *ConfigManager) refreshServerConfig(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case http.MethodPost: + vpnConfig, err := wireguard.GetVPNConfig(c.Storage) + if err != nil { + returnError(w, fmt.Errorf("get vpn config error: %s", err), http.StatusBadRequest) + return + } + c.VPNConfig.EnablePacketLogs = vpnConfig.EnablePacketLogs + c.VPNConfig.PacketLogsTypes = vpnConfig.PacketLogsTypes + w.WriteHeader(http.StatusAccepted) + default: + returnError(w, fmt.Errorf("method not supported"), http.StatusBadRequest) + } +} func (c *ConfigManager) upgrade(w http.ResponseWriter, r *http.Request) { switch r.Method { diff --git a/pkg/configmanager/router.go b/pkg/configmanager/router.go index 59a32d8..7b4f2b2 100644 --- a/pkg/configmanager/router.go +++ b/pkg/configmanager/router.go @@ -7,6 +7,7 @@ func (c *ConfigManager) getRouter() *http.ServeMux { mux.Handle("/pubkey", http.HandlerFunc(c.getPubKey)) mux.Handle("/refresh-clients", http.HandlerFunc(c.refreshClients)) + mux.Handle("/refresh-server-config", http.HandlerFunc(c.refreshServerConfig)) mux.Handle("/upgrade", http.HandlerFunc(c.upgrade)) mux.Handle("/restart-vpn", http.HandlerFunc(c.restartVpn)) mux.Handle("/version", http.HandlerFunc(c.version)) diff --git a/pkg/configmanager/server.go b/pkg/configmanager/server.go index 2fe4bca..3ef2580 100644 --- a/pkg/configmanager/server.go +++ b/pkg/configmanager/server.go @@ -41,8 +41,8 @@ func StartServer(port int) { } // start goroutines - startStats(localStorage) // start gathering of wireguard stats - startPacketLogger(localStorage, c.ClientCache) // start packet logger (optional) + startStats(localStorage) // start gathering of wireguard stats + startPacketLogger(localStorage, c.ClientCache, c.VPNConfig) // start packet logger (optional) log.Printf("Starting localhost http server at port %d\n", port) log.Fatal(http.ListenAndServe(fmt.Sprintf("127.0.0.1:%d", port), c.getRouter())) @@ -72,5 +72,7 @@ func initConfigManager(storage storage.Iface) (*ConfigManager, error) { } } + c.VPNConfig = &vpnConfig + return c, nil } diff --git a/pkg/configmanager/start_darwin.go b/pkg/configmanager/start_darwin.go index 0617e7d..db6fba3 100644 --- a/pkg/configmanager/start_darwin.go +++ b/pkg/configmanager/start_darwin.go @@ -24,6 +24,6 @@ func startStats(storage storage.Iface) { fmt.Printf("Warning: startStats is not implemented in darwin\n") } -func startPacketLogger(storage storage.Iface, clientCache *wireguard.ClientCache) { - go wireguard.RunPacketLogger(storage, clientCache) +func startPacketLogger(storage storage.Iface, clientCache *wireguard.ClientCache, vpnConfig *wireguard.VPNConfig) { + go wireguard.RunPacketLogger(storage, clientCache, vpnConfig) } diff --git a/pkg/configmanager/start_linux.go b/pkg/configmanager/start_linux.go index 8bba39d..65e0639 100644 --- a/pkg/configmanager/start_linux.go +++ b/pkg/configmanager/start_linux.go @@ -28,7 +28,7 @@ func startStats(storage storage.Iface) { go wireguard.RunStats(storage) } -func startPacketLogger(storage storage.Iface, clientCache *wireguard.ClientCache) { +func startPacketLogger(storage storage.Iface, clientCache *wireguard.ClientCache, vpnConfig *wireguard.VPNConfig) { // run statistics go routine - go wireguard.RunPacketLogger(storage, clientCache) + go wireguard.RunPacketLogger(storage, clientCache, vpnConfig) } diff --git a/pkg/configmanager/types.go b/pkg/configmanager/types.go index 10f00a3..0546805 100644 --- a/pkg/configmanager/types.go +++ b/pkg/configmanager/types.go @@ -10,6 +10,7 @@ type ConfigManager struct { PublicKey string Storage storage.Iface ClientCache *wireguard.ClientCache + VPNConfig *wireguard.VPNConfig } type UpgradeResponse struct { diff --git a/pkg/rest/router.go b/pkg/rest/router.go index 1206d01..227e688 100644 --- a/pkg/rest/router.go +++ b/pkg/rest/router.go @@ -63,6 +63,7 @@ func (c *Context) getRouter(assets fs.FS, indexHtml []byte) *http.ServeMux { mux.Handle("/api/users", c.authMiddleware(c.injectUserMiddleware(c.isAdminMiddleware(http.HandlerFunc(c.usersHandler))))) mux.Handle("/api/user/{id}", c.authMiddleware(c.injectUserMiddleware(c.isAdminMiddleware(http.HandlerFunc(c.userHandler))))) mux.Handle("/api/stats/user/{date}", c.authMiddleware(c.injectUserMiddleware(c.isAdminMiddleware(http.HandlerFunc(c.userStatsHandler))))) + mux.Handle("/api/stats/{user}/{date}", c.authMiddleware(c.injectUserMiddleware(c.isAdminMiddleware(http.HandlerFunc(c.ipLogsHandler))))) return mux } diff --git a/pkg/rest/setup.go b/pkg/rest/setup.go index 236c1c9..e588da6 100644 --- a/pkg/rest/setup.go +++ b/pkg/rest/setup.go @@ -9,6 +9,8 @@ import ( "net/http" "net/netip" "reflect" + "slices" + "sort" "strconv" "strings" "time" @@ -258,6 +260,26 @@ func (c *Context) vpnSetupHandler(w http.ResponseWriter, r *http.Request) { vpnConfig.DisableNAT = setupRequest.DisableNAT writeVPNConfig = true } + if setupRequest.EnablePacketLogs != vpnConfig.EnablePacketLogs { + vpnConfig.EnablePacketLogs = setupRequest.EnablePacketLogs + writeVPNConfig = true + } + // packetlogtypes + packetLogTypes := []string{} + for k, enabled := range vpnConfig.PacketLogsTypes { + if enabled { + packetLogTypes = append(packetLogTypes, k) + } + } + sort.Strings(setupRequest.PacketLogsTypes) + sort.Strings(packetLogTypes) + if !slices.Equal(setupRequest.PacketLogsTypes, packetLogTypes) { + vpnConfig.PacketLogsTypes = make(map[string]bool) + for _, v := range setupRequest.PacketLogsTypes { + vpnConfig.PacketLogsTypes[v] = true + } + writeVPNConfig = true + } // write vpn config if config has changed if writeVPNConfig { diff --git a/pkg/rest/stats.go b/pkg/rest/stats.go index ae6ccd0..e1efccd 100644 --- a/pkg/rest/stats.go +++ b/pkg/rest/stats.go @@ -210,6 +210,105 @@ func (c *Context) userStatsHandler(w http.ResponseWriter, r *http.Request) { c.write(w, out) } +func (c *Context) ipLogsHandler(w http.ResponseWriter, r *http.Request) { + vpnConfig, err := wireguard.GetVPNConfig(c.Storage.Client) + if err != nil { + c.returnError(w, fmt.Errorf("get vpn config error: %s", err), http.StatusBadRequest) + return + } + if !vpnConfig.EnablePacketLogs { // packet logs is disabled + out, err := json.Marshal(LogDataResponse{Enabled: false}) + if err != nil { + c.returnError(w, fmt.Errorf("user stats response marshal error: %s", err), http.StatusBadRequest) + return + } + c.write(w, out) + return + } + userID := r.PathValue("user") + if userID == "" { + c.returnError(w, fmt.Errorf("no user supplied"), http.StatusBadRequest) + return + } + if r.PathValue("date") == "" { + c.returnError(w, fmt.Errorf("no date supplied"), http.StatusBadRequest) + return + } + date, err := time.Parse("2006-01-02", r.PathValue("date")) + if err != nil { + c.returnError(w, fmt.Errorf("invalid date: %s", err), http.StatusBadRequest) + return + } + offset := 0 + if r.FormValue("offset") != "" { + i, err := strconv.Atoi(r.FormValue("offset")) + if err == nil { + offset = i + } + } + // get all users + users := c.UserStore.ListUsers() + userMap := make(map[string]string) + for _, user := range users { + userMap[user.ID] = user.Login + } + // calculate stats + statsFiles := []string{ + path.Join(wireguard.VPN_STATS_DIR, wireguard.VPN_PACKETLOGGER_DIR, userID+"-"+date.Format("2006-01-02")+".log"), + } + logInputData := bytes.NewBuffer([]byte{}) + for _, statsFile := range statsFiles { + if c.Storage.Client.FileExists(statsFile) { + fileLogData, err := c.Storage.Client.ReadFile(statsFile) + if err != nil { + c.returnError(w, fmt.Errorf("readfile error: %s", err), http.StatusBadRequest) + return + } + logInputData.Write(fileLogData) + } + } + + scanner := bufio.NewScanner(logInputData) + + logData := LogData{ + Schema: LogSchema{ + Columns: map[string]string{ + "Protocol": "string", + "Source IP": "string", + "Destination IP": "string", + "Source Port": "string", + "Destination Port": "string", + "Destination": "string", + }, + }, + } + + for scanner.Scan() { // all other entries + inputSplit := strings.Split(scanner.Text(), ",") + timestamp, err := time.Parse(wireguard.TIMESTAMP_FORMAT, inputSplit[0]) + if err != nil { + continue // invalid record + } + row := LogRow{ + Timestamp: timestamp.Add(time.Duration(offset) * time.Minute), + Data: inputSplit[1:], + } + logData.Data = append(logData.Data, row) + } + + if err := scanner.Err(); err != nil { + c.returnError(w, fmt.Errorf("log file read (scanner) error: %s", err), http.StatusBadRequest) + return + } + + out, err := json.Marshal(LogDataResponse{Enabled: true, LogData: logData}) + if err != nil { + c.returnError(w, fmt.Errorf("user stats response marshal error: %s", err), http.StatusBadRequest) + return + } + c.write(w, out) +} + func getColor(i int) string { colors := []string{ "#DEEFB7", diff --git a/pkg/rest/types.go b/pkg/rest/types.go index ff7fdc0..7ecbd76 100644 --- a/pkg/rest/types.go +++ b/pkg/rest/types.go @@ -102,14 +102,16 @@ type GeneralSetupRequest struct { } type VPNSetupRequest struct { - Routes string `json:"routes"` - VPNEndpoint string `json:"vpnEndpoint"` - AddressRange string `json:"addressRange"` - ClientAddressPrefix string `json:"clientAddressPrefix"` - Port string `json:"port"` - ExternalInterface string `json:"externalInterface"` - Nameservers string `json:"nameservers"` - DisableNAT bool `json:"disableNAT"` + Routes string `json:"routes"` + VPNEndpoint string `json:"vpnEndpoint"` + AddressRange string `json:"addressRange"` + ClientAddressPrefix string `json:"clientAddressPrefix"` + Port string `json:"port"` + ExternalInterface string `json:"externalInterface"` + Nameservers string `json:"nameservers"` + DisableNAT bool `json:"disableNAT"` + EnablePacketLogs bool `json:"enablePacketLogs"` + PacketLogsTypes []string `json:"packetLogsTypes"` } type TemplateSetupRequest struct { @@ -205,3 +207,20 @@ type NewUserRequest struct { Role string `json:"role"` Password string `json:"password,omitempty"` } + +type LogDataResponse struct { + LogData LogData `json:"logData"` + Enabled bool `json:"enabled"` +} + +type LogData struct { + Schema LogSchema `json:"schema"` + Data []LogRow `json:"rows"` +} +type LogSchema struct { + Columns map[string]string `json:"columns"` +} +type LogRow struct { + Timestamp time.Time `json:"t"` + Data []string `json:"d"` +} diff --git a/pkg/rest/users_test.go b/pkg/rest/users_test.go index c95e920..99652d6 100644 --- a/pkg/rest/users_test.go +++ b/pkg/rest/users_test.go @@ -32,6 +32,11 @@ func TestCreateUserConnectionDeleteUserFlow(t *testing.T) { w.Write([]byte("OK")) return } + if r.RequestURI == "/refresh-server-config" { + w.WriteHeader(http.StatusAccepted) + w.Write([]byte("OK")) + return + } w.WriteHeader(http.StatusBadRequest) default: w.WriteHeader(http.StatusBadRequest) diff --git a/pkg/wireguard/constants.go b/pkg/wireguard/constants.go index e2bce0e..26beba2 100644 --- a/pkg/wireguard/constants.go +++ b/pkg/wireguard/constants.go @@ -8,6 +8,7 @@ const VPN_CONFIG_NAME = "vpn-config.json" const IP_LIST_PATH = "config/iplist.json" const VPN_CLIENTS_DIR = "clients" const VPN_STATS_DIR = "stats" +const VPN_PACKETLOGGER_DIR = "packetlogs" const VPN_SERVER_SECRETS_PATH = "secrets" const VPN_PRIVATE_KEY_FILENAME = "priv.key" const PRESHARED_KEY_FILENAME = "preshared.key" diff --git a/pkg/wireguard/packetlogger.go b/pkg/wireguard/packetlogger.go index 509f3cb..ef51b5e 100644 --- a/pkg/wireguard/packetlogger.go +++ b/pkg/wireguard/packetlogger.go @@ -22,7 +22,32 @@ import ( "golang.org/x/sys/unix" ) -func RunPacketLogger(storage storage.Iface, clientCache *ClientCache) { +func RunPacketLogger(storage storage.Iface, clientCache *ClientCache, vpnConfig *VPNConfig) { + if !vpnConfig.EnablePacketLogs { + return + } + // ensure logs dir is created + err := storage.EnsurePath(VPN_STATS_DIR) + if err != nil { + logging.ErrorLog(fmt.Errorf("could not create stats path: %s. Stats disabled", err)) + return + } + err = storage.EnsureOwnership(VPN_STATS_DIR, "vpn") + if err != nil { + logging.ErrorLog(fmt.Errorf("could not ensure ownership of stats path: %s. Stats disabled", err)) + return + } + err = storage.EnsurePath(path.Join(VPN_STATS_DIR, VPN_PACKETLOGGER_DIR)) + if err != nil { + logging.ErrorLog(fmt.Errorf("could not create stats path: %s. Stats disabled", err)) + return + } + err = storage.EnsureOwnership(path.Join(VPN_STATS_DIR, VPN_PACKETLOGGER_DIR), "vpn") + if err != nil { + logging.ErrorLog(fmt.Errorf("could not ensure ownership of stats path: %s. Stats disabled", err)) + return + } + useSyscalls := false if runtime.GOOS == "darwin" { useSyscalls = true @@ -38,6 +63,10 @@ func RunPacketLogger(storage storage.Iface, clientCache *ClientCache) { if err != nil { logging.DebugLog(fmt.Errorf("readPacket error: %s", err)) } + if !vpnConfig.EnablePacketLogs { + logging.InfoLog("disabling packetlogs") + return + } if i%1000 == 0 { if err := checkDiskSpace(); err != nil { logging.ErrorLog(fmt.Errorf("disk space error: %s", err)) @@ -56,8 +85,6 @@ func readPacket(storage storage.Iface, handle *pcap.Handle, clientCache *ClientC return parsePacket(storage, data, clientCache) } func parsePacket(storage storage.Iface, data []byte, clientCache *ClientCache) error { - now := time.Now() - filename := path.Join(VPN_STATS_DIR, "ip-"+now.Format("2006-01-02.log")) packet := gopacket.NewPacket(data, layers.IPProtocolIPv4, gopacket.DecodeOptions{Lazy: true, DecodeStreamsAsDatagrams: true}) var ( ip4 *layers.IPv4 @@ -89,6 +116,8 @@ func parsePacket(storage storage.Iface, data []byte, clientCache *ClientCache) e if clientID == "" { // doesn't match a client ID return nil } + now := time.Now() + filename := path.Join(VPN_STATS_DIR, VPN_PACKETLOGGER_DIR, clientID+"-"+now.Format("2006-01-02")+".log") if tcpLayer := packet.Layer(layers.LayerTypeTCP); tcpLayer != nil { tcpPacket, _ := tcpLayer.(*layers.TCP) if tcpPacket.SYN { diff --git a/pkg/wireguard/packetlogger_test.go b/pkg/wireguard/packetlogger_test.go index 6c0c000..4728578 100644 --- a/pkg/wireguard/packetlogger_test.go +++ b/pkg/wireguard/packetlogger_test.go @@ -20,14 +20,14 @@ func TestParsePacket(t *testing.T) { IP: net.ParseIP("10.189.184.2"), Mask: net.IPMask(net.ParseIP("255.255.255.255").To4()), }, - ClientID: "1-2-3-4-1", + ClientID: "1-2-3-4", }, { Address: net.IPNet{ IP: net.ParseIP("10.189.184.3"), Mask: net.IPMask(net.ParseIP("255.255.255.255").To4()), }, - ClientID: "1-2-3-5-1", + ClientID: "1-2-3-5", }, }, } @@ -62,7 +62,7 @@ func TestParsePacket(t *testing.T) { } } - out, err := storage.ReadFile(path.Join(VPN_STATS_DIR, "ip-"+now.Format("2006-01-02.log"))) + out, err := storage.ReadFile(path.Join(VPN_STATS_DIR, VPN_PACKETLOGGER_DIR, "1-2-3-4-"+now.Format("2006-01-02")+".log")) if err != nil { t.Fatalf("read file error: %s", err) } @@ -83,14 +83,14 @@ func TestParsePacketSNI(t *testing.T) { IP: net.ParseIP("10.189.184.2"), Mask: net.IPMask(net.ParseIP("255.255.255.255").To4()), }, - ClientID: "1-2-3-4-1", + ClientID: "1-2-3-4", }, { Address: net.IPNet{ IP: net.ParseIP("10.189.184.3"), Mask: net.IPMask(net.ParseIP("255.255.255.255").To4()), }, - ClientID: "1-2-3-5-1", + ClientID: "1-2-3-5", }, }, } @@ -110,7 +110,7 @@ func TestParsePacketSNI(t *testing.T) { } } - out, err := storage.ReadFile(path.Join(VPN_STATS_DIR, "ip-"+now.Format("2006-01-02.log"))) + out, err := storage.ReadFile(path.Join(VPN_STATS_DIR, VPN_PACKETLOGGER_DIR, "1-2-3-4-"+now.Format("2006-01-02")+".log")) if err != nil { t.Fatalf("read file error: %s", err) } diff --git a/pkg/wireguard/types.go b/pkg/wireguard/types.go index 75cca29..7632da3 100644 --- a/pkg/wireguard/types.go +++ b/pkg/wireguard/types.go @@ -32,16 +32,18 @@ type VPNServerClient struct { } type VPNConfig struct { - AddressRange netip.Prefix `json:"addressRange"` - ClientAddressPrefix string `json:"clientAddressPrefix"` - PublicKey string `json:"publicKey"` - PresharedKey string `json:"presharedKey"` - Endpoint string `json:"endpoint"` - Port int `json:"port"` - ExternalInterface string `json:"externalInterface"` - Nameservers []string `json:"nameservers"` - DisableNAT bool `json:"disableNAT"` - ClientRoutes []string `json:"clientRoutes"` + AddressRange netip.Prefix `json:"addressRange"` + ClientAddressPrefix string `json:"clientAddressPrefix"` + PublicKey string `json:"publicKey"` + PresharedKey string `json:"presharedKey"` + Endpoint string `json:"endpoint"` + Port int `json:"port"` + ExternalInterface string `json:"externalInterface"` + Nameservers []string `json:"nameservers"` + DisableNAT bool `json:"disableNAT"` + ClientRoutes []string `json:"clientRoutes"` + EnablePacketLogs bool `json:"enablePacketLogs"` + PacketLogsTypes map[string]bool `json:"packetLogsTypes"` } type PubKeyExchange struct { diff --git a/pkg/wireguard/vpnconfig.go b/pkg/wireguard/vpnconfig.go index 2d819a5..e56fe3b 100644 --- a/pkg/wireguard/vpnconfig.go +++ b/pkg/wireguard/vpnconfig.go @@ -44,16 +44,26 @@ func GetVPNConfig(storage storage.Iface) (VPNConfig, error) { if err != nil { return vpnConfig, fmt.Errorf("decode input error: %s", err) } + + if vpnConfig.PacketLogsTypes == nil { + vpnConfig.PacketLogsTypes = make(map[string]bool) + } + return vpnConfig, nil } func getEmptyVPNConfig() (VPNConfig, error) { - vpnConfig := VPNConfig{} + vpnConfig := VPNConfig{ + PacketLogsTypes: make(map[string]bool), + } return vpnConfig, nil } func CreateNewVPNConfig(storage storage.Iface) (VPNConfig, error) { - vpnConfig := VPNConfig{} + vpnConfig, err := getEmptyVPNConfig() + if err != nil { + return vpnConfig, fmt.Errorf("get empty vpn config error: %s", err) + } prefix, err := netip.ParsePrefix(DEFAULT_VPN_PREFIX) if err != nil { return vpnConfig, fmt.Errorf("ParsePrefix error: %s", err) @@ -135,6 +145,19 @@ func WriteVPNConfig(storage storage.Iface, vpnConfig VPNConfig) error { } } + // notify configmanager + client := http.Client{ + Timeout: 10 * time.Second, + } + + resp, err := client.Post("http://"+CONFIGMANAGER_URI+"/refresh-server-config", "application/json", nil) + if err != nil { + return fmt.Errorf("configmanager post error: %s", err) + } + if resp.StatusCode != http.StatusAccepted { + return fmt.Errorf("configmanager post error: received status code %d", resp.StatusCode) + } + return nil } diff --git a/pkg/wireguard/wireguardclientconfig_test.go b/pkg/wireguard/wireguardclientconfig_test.go index 5c1d346..df266de 100644 --- a/pkg/wireguard/wireguardclientconfig_test.go +++ b/pkg/wireguard/wireguardclientconfig_test.go @@ -80,6 +80,11 @@ func TestWriteConfig(t *testing.T) { w.Write([]byte("OK")) return } + if r.RequestURI == "/refresh-server-config" { + w.WriteHeader(http.StatusAccepted) + w.Write([]byte("OK")) + return + } w.WriteHeader(http.StatusBadRequest) default: w.WriteHeader(http.StatusBadRequest) @@ -152,6 +157,11 @@ func TestWriteConfigMultipleClients(t *testing.T) { w.Write([]byte("OK")) return } + if r.RequestURI == "/refresh-server-config" { + w.WriteHeader(http.StatusAccepted) + w.Write([]byte("OK")) + return + } w.WriteHeader(http.StatusBadRequest) default: w.WriteHeader(http.StatusBadRequest) @@ -226,6 +236,11 @@ func TestCreateAndDeleteAllClientConfig(t *testing.T) { w.Write([]byte("OK")) return } + if r.RequestURI == "/refresh-server-config" { + w.WriteHeader(http.StatusAccepted) + w.Write([]byte("OK")) + return + } w.WriteHeader(http.StatusBadRequest) default: w.WriteHeader(http.StatusBadRequest) @@ -314,6 +329,11 @@ func TestCreateAndDeleteClientConfig(t *testing.T) { w.Write([]byte("OK")) return } + if r.RequestURI == "/refresh-server-config" { + w.WriteHeader(http.StatusAccepted) + w.Write([]byte("OK")) + return + } w.WriteHeader(http.StatusBadRequest) default: w.WriteHeader(http.StatusBadRequest) @@ -403,6 +423,11 @@ func TestCreateAndDisableAllClientConfig(t *testing.T) { w.Write([]byte("OK")) return } + if r.RequestURI == "/refresh-server-config" { + w.WriteHeader(http.StatusAccepted) + w.Write([]byte("OK")) + return + } w.WriteHeader(http.StatusBadRequest) default: w.WriteHeader(http.StatusBadRequest) @@ -527,6 +552,11 @@ func TestUpdateClientConfig(t *testing.T) { w.Write([]byte("OK")) return } + if r.RequestURI == "/refresh-server-config" { + w.WriteHeader(http.StatusAccepted) + w.Write([]byte("OK")) + return + } w.WriteHeader(http.StatusBadRequest) default: w.WriteHeader(http.StatusBadRequest) @@ -610,6 +640,11 @@ func TestUpdateClientConfigNewAddressRange(t *testing.T) { w.Write([]byte("OK")) return } + if r.RequestURI == "/refresh-server-config" { + w.WriteHeader(http.StatusAccepted) + w.Write([]byte("OK")) + return + } w.WriteHeader(http.StatusBadRequest) default: w.WriteHeader(http.StatusBadRequest) @@ -722,6 +757,11 @@ func TestUpdateClientConfigNewClientAddressPrefix(t *testing.T) { w.Write([]byte("OK")) return } + if r.RequestURI == "/refresh-server-config" { + w.WriteHeader(http.StatusAccepted) + w.Write([]byte("OK")) + return + } w.WriteHeader(http.StatusBadRequest) default: w.WriteHeader(http.StatusBadRequest) diff --git a/pkg/wireguard/wireguardserverconfig_test.go b/pkg/wireguard/wireguardserverconfig_test.go index f791d75..40f2a2c 100644 --- a/pkg/wireguard/wireguardserverconfig_test.go +++ b/pkg/wireguard/wireguardserverconfig_test.go @@ -1,13 +1,58 @@ package wireguard import ( + "net" + "net/http" + "net/http/httptest" "strings" "testing" + "time" testingmocks "github.com/in4it/wireguard-server/pkg/testing/mocks" ) func TestWriteWireGuardServerConfig(t *testing.T) { + var ( + l net.Listener + err error + ) + for { + l, err = net.Listen("tcp", CONFIGMANAGER_URI) + if err != nil { + if !strings.HasSuffix(err.Error(), "address already in use") { + t.Fatal(err) + } + time.Sleep(1 * time.Second) + } else { + break + } + } + + ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case http.MethodPost: + if r.RequestURI == "/refresh-clients" { + w.WriteHeader(http.StatusAccepted) + w.Write([]byte("OK")) + return + } + if r.RequestURI == "/refresh-server-config" { + w.WriteHeader(http.StatusAccepted) + w.Write([]byte("OK")) + return + } + w.WriteHeader(http.StatusBadRequest) + default: + w.WriteHeader(http.StatusBadRequest) + } + })) + + ts.Listener.Close() + ts.Listener = l + ts.Start() + defer ts.Close() + defer l.Close() + storage := &testingmocks.MockMemoryStorage{} // first create a new vpn config diff --git a/webapp/src/Routes/Setup/VPNSetup.tsx b/webapp/src/Routes/Setup/VPNSetup.tsx index 952c2c2..5338660 100644 --- a/webapp/src/Routes/Setup/VPNSetup.tsx +++ b/webapp/src/Routes/Setup/VPNSetup.tsx @@ -1,5 +1,5 @@ -import { Container, TextInput, Alert, InputWrapper, Button, Space, UnstyledButton, Checkbox, Text } from "@mantine/core"; +import { Container, TextInput, Alert, InputWrapper, Button, Space, UnstyledButton, Checkbox, Text, MultiSelect } from "@mantine/core"; import { useEffect, useState } from "react"; import classes from './Setup.module.css'; import { IconInfoCircle } from "@tabler/icons-react"; @@ -23,6 +23,8 @@ type VPNSetupRequest = { externalInterface: string, nameservers: string, disableNAT: boolean, + enablePacketLogs: boolean, + packetLogTypes: string[], }; export function VPNSetup() { const [saved, setSaved] = useState(false) @@ -53,7 +55,9 @@ export function VPNSetup() { port: "", externalInterface: "", nameservers: "", - disableNAT: false, + disableNAT: false, + enablePacketLogs: false, + packetLogTypes: [] }, }); const setupMutation = useMutation({ @@ -217,8 +221,41 @@ export function VPNSetup() { - - + + form.setFieldValue("enablePacketLogs", !form.getValues().enablePacketLogs )}> + +
+ + Enable IP Packet logging + + + Metadata of IP packets passing the VPN can be logged and displayed in this admin portal. Useful if you want to see TCP connection requests, DNS requests, or http/https requests passing the VPN. + +
+
+ {form.getValues().enablePacketLogs ? + + + + : null} +