Skip to content

Commit

Permalink
wireguard user stats (#12)
Browse files Browse the repository at this point in the history
user stats
  • Loading branch information
wardviaene authored Aug 26, 2024
1 parent 108a0f7 commit 0a945a3
Show file tree
Hide file tree
Showing 38 changed files with 948 additions and 56 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/config/*
/stats/*
/templates/*
/rest-server
/o-rest-server
Expand Down
2 changes: 1 addition & 1 deletion latest
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v1.1.0
v1.1.1
2 changes: 1 addition & 1 deletion pkg/configmanager/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ func (c *ConfigManager) version(w http.ResponseWriter, r *http.Request) {
func (c *ConfigManager) restartVpn(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodPost:
err := stopVPN(c.Storage)
err := stopVPN()
if err != nil { // don't exit, as the VPN might be down already.
fmt.Println("========= Warning =========")
fmt.Printf("Warning: vpn stop error: %s\n", err)
Expand Down
2 changes: 2 additions & 0 deletions pkg/configmanager/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ func StartServer(port int) {
log.Fatalf("could not refresh all clients: %s", err)
}

startStats(localStorage) // start gathering of wireguard stats

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()))
}
Expand Down
6 changes: 5 additions & 1 deletion pkg/configmanager/start_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ func startVPN(storage storage.Iface) error {
return nil
}

func stopVPN(storage storage.Iface) error {
func stopVPN() error {
fmt.Printf("Warning: startVPN is not implemented in darwin\n")
return nil
}

func startStats(storage storage.Iface) {
fmt.Printf("Warning: startStats is not implemented in darwin\n")
}
8 changes: 7 additions & 1 deletion pkg/configmanager/start_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,15 @@ func startVPN(storage storage.Iface) error {
if err != nil {
log.Fatalf("WriteWireGuardServerConfig error: %s", err)
}

return wireguard.StartVPN()
}

func stopVPN(storage storage.Iface) error {
func stopVPN() error {
return wireguard.StopVPN()
}

func startStats(storage storage.Iface) {
// run statistics go routine
go wireguard.RunStats(storage)
}
4 changes: 3 additions & 1 deletion pkg/configmanager/types.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package configmanager

import "github.com/in4it/wireguard-server/pkg/storage"
import (
"github.com/in4it/wireguard-server/pkg/storage"
)

type ConfigManager struct {
PrivateKey string
Expand Down
4 changes: 2 additions & 2 deletions pkg/logging/log.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ const LOG_DEBUG = 16

func DebugLog(err error) {
if Loglevel&LOG_DEBUG == LOG_DEBUG {
fmt.Println(err)
fmt.Println("debug: " + err.Error())
}
}

func ErrorLog(err error) {
if Loglevel&LOG_ERROR == LOG_ERROR {
fmt.Println("debug: " + err.Error())
fmt.Println("error: " + err.Error())
}
}

Expand Down
39 changes: 39 additions & 0 deletions pkg/rest/auditlog/logentry.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package auditlog

import (
"encoding/json"
"fmt"
"path"
"time"

"github.com/in4it/wireguard-server/pkg/storage"
)

const TIMESTAMP_FORMAT = "2006-01-02T15:04:05"
const AUDITLOG_STATS_DIR = "stats"

type LogEntry struct {
Timestamp LogTimestamp `json:"timestamp"`
UserID string `json:"userID"`
Action string `json:"action"`
}
type LogTimestamp time.Time

func (t LogTimestamp) MarshalJSON() ([]byte, error) {
timestamp := fmt.Sprintf("\"%s\"", time.Time(t).Format(TIMESTAMP_FORMAT))
return []byte(timestamp), nil
}

func Write(storage storage.Iface, logEntry LogEntry) error {
statsPath := path.Join(AUDITLOG_STATS_DIR, "logins-"+time.Now().Format("2006-01-02")) + ".log"
logEntryBytes, err := json.Marshal(logEntry)
if err != nil {
return fmt.Errorf("could not parse log entry: %s", err)
}
err = storage.AppendFile(statsPath, logEntryBytes)
if err != nil {
return fmt.Errorf("could not append stats to file (%s): %s", statsPath, err)
}

return nil
}
8 changes: 7 additions & 1 deletion pkg/rest/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/in4it/wireguard-server/pkg/auth/oidc"
oidcstore "github.com/in4it/wireguard-server/pkg/auth/oidc/store"
"github.com/in4it/wireguard-server/pkg/auth/saml"
"github.com/in4it/wireguard-server/pkg/logging"
"github.com/in4it/wireguard-server/pkg/rest/login"
)

Expand Down Expand Up @@ -40,7 +41,7 @@ func (c *Context) authHandler(w http.ResponseWriter, r *http.Request) {
return
}

loginResponse, err := login.Authenticate(loginReq, c.UserStore, c.JWTKeys.PrivateKey, c.JWTKeysKID)
loginResponse, user, err := login.Authenticate(loginReq, c.UserStore, c.JWTKeys.PrivateKey, c.JWTKeysKID)
if err != nil {
c.returnError(w, fmt.Errorf("authentication error: %s", err), http.StatusBadRequest)
return
Expand All @@ -55,6 +56,11 @@ func (c *Context) authHandler(w http.ResponseWriter, r *http.Request) {
return
} else if loginResponse.Authenticated {
login.ClearAttemptsForLogin(c.LoginAttempts, loginReq.Login)
user.LastLogin = time.Now()
err = c.UserStore.UpdateUser(user)
if err != nil {
logging.ErrorLog(fmt.Errorf("last login update error: %s", err))
}
c.write(w, out)
} else {
// log login attempts
Expand Down
11 changes: 6 additions & 5 deletions pkg/rest/login/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,17 @@ import (
"fmt"

"github.com/in4it/wireguard-server/pkg/mfa/totp"
"github.com/in4it/wireguard-server/pkg/users"
)

func Authenticate(loginReq LoginRequest, authIface AuthIface, jwtPrivateKey *rsa.PrivateKey, jwtKeyID string) (LoginResponse, error) {
func Authenticate(loginReq LoginRequest, authIface AuthIface, jwtPrivateKey *rsa.PrivateKey, jwtKeyID string) (LoginResponse, users.User, error) {
loginResponse := LoginResponse{}
user, auth := authIface.AuthUser(loginReq.Login, loginReq.Password)
if auth && !user.Suspended {
if len(user.Factors) == 0 { // authentication without MFA
token, err := GetJWTToken(user.Login, user.Role, jwtPrivateKey, jwtKeyID)
if err != nil {
return loginResponse, fmt.Errorf("token generation failed: %s", err)
return loginResponse, user, fmt.Errorf("token generation failed: %s", err)
}
loginResponse.Authenticated = true
loginResponse.Token = token
Expand All @@ -30,12 +31,12 @@ func Authenticate(loginReq LoginRequest, authIface AuthIface, jwtPrivateKey *rsa
if factor.Name == loginReq.FactorResponse.Name {
ok, err := totp.Verify(factor.Secret, loginReq.FactorResponse.Code)
if err != nil {
return loginResponse, fmt.Errorf("MFA (totp) verify failed: %s", err)
return loginResponse, user, fmt.Errorf("MFA (totp) verify failed: %s", err)
}
if ok { // authentication with MFA
token, err := GetJWTToken(user.Login, user.Role, jwtPrivateKey, jwtKeyID)
if err != nil {
return loginResponse, fmt.Errorf("token generation failed: %s", err)
return loginResponse, user, fmt.Errorf("token generation failed: %s", err)
}
loginResponse.Authenticated = true
loginResponse.Token = token
Expand All @@ -45,5 +46,5 @@ func Authenticate(loginReq LoginRequest, authIface AuthIface, jwtPrivateKey *rsa
}
}
}
return loginResponse, nil
return loginResponse, user, nil
}
6 changes: 3 additions & 3 deletions pkg/rest/login/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func TestAuthenticate(t *testing.T) {
t.Fatalf("private key error: %s", err)
}

loginResp, err := Authenticate(loginReq, &m, privateKey, "jwtKeyID")
loginResp, _, err := Authenticate(loginReq, &m, privateKey, "jwtKeyID")
if err != nil {
t.Fatalf("authentication error: %s", err)
}
Expand Down Expand Up @@ -70,7 +70,7 @@ func TestAuthenticateMFANoToken(t *testing.T) {
t.Fatalf("private key error: %s", err)
}

loginResp, err := Authenticate(loginReq, &m, privateKey, "jwtKeyID")
loginResp, _, err := Authenticate(loginReq, &m, privateKey, "jwtKeyID")
if err != nil {
t.Fatalf("authentication error: %s", err)
}
Expand Down Expand Up @@ -113,7 +113,7 @@ func TestAuthenticateMFAWithToken(t *testing.T) {
t.Fatalf("private key error: %s", err)
}

loginResp, err := Authenticate(loginReq, &m, privateKey, "jwtKeyID")
loginResp, _, err := Authenticate(loginReq, &m, privateKey, "jwtKeyID")
if err != nil {
t.Fatalf("authentication error: %s", err)
}
Expand Down
1 change: 1 addition & 0 deletions pkg/rest/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ func (c *Context) getRouter(assets fs.FS, indexHtml []byte) *http.ServeMux {
mux.Handle("/api/saml-setup/{id}", c.authMiddleware(c.injectUserMiddleware(c.isAdminMiddleware(http.HandlerFunc(c.samlSetupElementHandler)))))
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)))))

return mux
}
Loading

0 comments on commit 0a945a3

Please sign in to comment.