mirror of
https://github.com/h44z/wg-portal.git
synced 2025-09-13 14:31:15 +00:00
initial commit
This commit is contained in:
242
internal/server/core.go
Normal file
242
internal/server/core.go
Normal file
@@ -0,0 +1,242 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
"errors"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/h44z/wg-portal/internal/wireguard"
|
||||
|
||||
"github.com/h44z/wg-portal/internal/common"
|
||||
|
||||
"github.com/h44z/wg-portal/internal/ldap"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/gin-gonic/contrib/sessions"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
const SessionIdentifier = "wgPortalSession"
|
||||
const CacheRefreshDuration = 5 * time.Minute
|
||||
|
||||
func init() {
|
||||
gob.Register(SessionData{})
|
||||
}
|
||||
|
||||
type SessionData struct {
|
||||
LoggedIn bool
|
||||
IsAdmin bool
|
||||
UID string
|
||||
UserName string
|
||||
Firstname string
|
||||
Lastname string
|
||||
SortedBy string
|
||||
SortDirection string
|
||||
Search string
|
||||
AlertData string
|
||||
AlertType string
|
||||
}
|
||||
|
||||
type AlertData struct {
|
||||
HasAlert bool
|
||||
Message string
|
||||
Type string
|
||||
}
|
||||
|
||||
type StaticData struct {
|
||||
WebsiteTitle string
|
||||
WebsiteLogo string
|
||||
LoginURL string
|
||||
LogoutURL string
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
// Core components
|
||||
config *common.Config
|
||||
server *gin.Engine
|
||||
users *UserManager
|
||||
|
||||
// WireGuard stuff
|
||||
wg *wireguard.Manager
|
||||
|
||||
// LDAP stuff
|
||||
ldapAuth ldap.Authentication
|
||||
ldapUsers *ldap.SynchronizedUserCacheHolder
|
||||
ldapCacheUpdater *ldap.UserCache
|
||||
}
|
||||
|
||||
func (s *Server) Setup() error {
|
||||
// Init rand
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
|
||||
s.config = common.NewConfig()
|
||||
|
||||
// Setup LDAP stuff
|
||||
s.ldapAuth = ldap.NewAuthentication(s.config.LDAP)
|
||||
s.ldapUsers = &ldap.SynchronizedUserCacheHolder{}
|
||||
s.ldapUsers.Init()
|
||||
s.ldapCacheUpdater = ldap.NewUserCache(s.config.LDAP, s.ldapUsers)
|
||||
if s.ldapCacheUpdater.LastError != nil {
|
||||
return s.ldapCacheUpdater.LastError
|
||||
}
|
||||
|
||||
// Setup WireGuard stuff
|
||||
s.wg = &wireguard.Manager{Cfg: &s.config.WG}
|
||||
if err := s.wg.Init(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Setup user manager
|
||||
s.users = NewUserManager()
|
||||
if s.users == nil {
|
||||
return errors.New("unable to setup user manager")
|
||||
}
|
||||
s.users.InitWithDevice(s.wg.GetDeviceInfo())
|
||||
s.users.InitWithPeers(s.wg.GetPeerList())
|
||||
|
||||
dir := s.getExecutableDirectory()
|
||||
rDir, _ := filepath.Abs(filepath.Dir(os.Args[0]))
|
||||
log.Infof("Real working directory: %s", rDir)
|
||||
log.Infof("Current working directory: %s", dir)
|
||||
|
||||
// Setup http server
|
||||
s.server = gin.Default()
|
||||
|
||||
// Setup templates
|
||||
log.Infof("Loading templates from: %s", filepath.Join(dir, "/assets/tpl/*.html"))
|
||||
s.server.LoadHTMLGlob(filepath.Join(dir, "/assets/tpl/*.html"))
|
||||
s.server.Use(sessions.Sessions("authsession", sessions.NewCookieStore([]byte("secret"))))
|
||||
|
||||
// Serve static files
|
||||
s.server.Static("/css", filepath.Join(dir, "/assets/css"))
|
||||
s.server.Static("/js", filepath.Join(dir, "/assets/js"))
|
||||
s.server.Static("/img", filepath.Join(dir, "/assets/img"))
|
||||
s.server.Static("/fonts", filepath.Join(dir, "/assets/fonts"))
|
||||
|
||||
// Setup all routes
|
||||
SetupRoutes(s)
|
||||
|
||||
log.Infof("Setup of service completed!")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) Run() {
|
||||
// Start ldap group watcher
|
||||
go func(s *Server) {
|
||||
for {
|
||||
time.Sleep(CacheRefreshDuration)
|
||||
if err := s.ldapCacheUpdater.Update(true); err != nil {
|
||||
log.Warnf("Failed to update ldap group cache: %v", err)
|
||||
}
|
||||
log.Debugf("Refreshed LDAP permissions!")
|
||||
}
|
||||
}(s)
|
||||
|
||||
// Run web service
|
||||
err := s.server.Run(s.config.Core.ListeningAddress)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to listen and serve on %s: %v", s.config.Core.ListeningAddress, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) getExecutableDirectory() string {
|
||||
dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
|
||||
if err != nil {
|
||||
log.Errorf("Failed to get executable directory: %v", err)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(filepath.Join(dir, "assets")); os.IsNotExist(err) {
|
||||
return "." // assets directory not found -> we are developing in goland =)
|
||||
}
|
||||
|
||||
return dir
|
||||
}
|
||||
|
||||
func (s *Server) getSessionData(c *gin.Context) SessionData {
|
||||
session := sessions.Default(c)
|
||||
rawSessionData := session.Get(SessionIdentifier)
|
||||
|
||||
var sessionData SessionData
|
||||
if rawSessionData != nil {
|
||||
sessionData = rawSessionData.(SessionData)
|
||||
} else {
|
||||
sessionData = SessionData{
|
||||
SortedBy: "sn",
|
||||
SortDirection: "asc",
|
||||
Firstname: "",
|
||||
Lastname: "",
|
||||
IsAdmin: false,
|
||||
LoggedIn: false,
|
||||
}
|
||||
session.Set(SessionIdentifier, sessionData)
|
||||
if err := session.Save(); err != nil {
|
||||
log.Errorf("Failed to store session: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return sessionData
|
||||
}
|
||||
|
||||
func (s *Server) getAlertData(c *gin.Context) AlertData {
|
||||
currentSession := s.getSessionData(c)
|
||||
alertData := AlertData{
|
||||
HasAlert: currentSession.AlertData != "",
|
||||
Message: currentSession.AlertData,
|
||||
Type: currentSession.AlertType,
|
||||
}
|
||||
// Reset alerts
|
||||
_ = s.setAlert(c, "", "")
|
||||
|
||||
return alertData
|
||||
}
|
||||
|
||||
func (s *Server) updateSessionData(c *gin.Context, data SessionData) error {
|
||||
session := sessions.Default(c)
|
||||
session.Set(SessionIdentifier, data)
|
||||
if err := session.Save(); err != nil {
|
||||
log.Errorf("Failed to store session: %v", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) destroySessionData(c *gin.Context) error {
|
||||
session := sessions.Default(c)
|
||||
session.Delete(SessionIdentifier)
|
||||
if err := session.Save(); err != nil {
|
||||
log.Errorf("Failed to destroy session: %v", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) getStaticData() StaticData {
|
||||
return StaticData{
|
||||
WebsiteTitle: s.config.Core.Title,
|
||||
LoginURL: s.config.AuthRoutePrefix + "/login",
|
||||
LogoutURL: s.config.AuthRoutePrefix + "/logout",
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) setAlert(c *gin.Context, message, typ string) SessionData {
|
||||
currentSession := s.getSessionData(c)
|
||||
currentSession.AlertData = message
|
||||
currentSession.AlertType = typ
|
||||
_ = s.updateSessionData(c, currentSession)
|
||||
|
||||
return currentSession
|
||||
}
|
||||
|
||||
func (s SessionData) GetSortIcon(field string) string {
|
||||
if s.SortedBy != field {
|
||||
return "fa-sort"
|
||||
}
|
||||
if s.SortDirection == "asc" {
|
||||
return "fa-sort-alpha-down"
|
||||
} else {
|
||||
return "fa-sort-alpha-up"
|
||||
}
|
||||
}
|
57
internal/server/handlers.go
Normal file
57
internal/server/handlers.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func (s *Server) GetIndex(c *gin.Context) {
|
||||
c.HTML(http.StatusOK, "index.html", gin.H{
|
||||
"route": c.Request.URL.Path,
|
||||
"session": s.getSessionData(c),
|
||||
"static": s.getStaticData(),
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Server) HandleError(c *gin.Context, code int, message, details string) {
|
||||
// TODO: if json
|
||||
//c.JSON(code, gin.H{"error": message, "details": details})
|
||||
|
||||
c.HTML(code, "error.html", gin.H{
|
||||
"data": gin.H{
|
||||
"Code": strconv.Itoa(code),
|
||||
"Message": message,
|
||||
"Details": details,
|
||||
},
|
||||
"route": c.Request.URL.Path,
|
||||
"session": s.getSessionData(c),
|
||||
"static": s.getStaticData(),
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Server) GetAdminIndex(c *gin.Context) {
|
||||
dev, err := s.wg.GetDeviceInfo()
|
||||
if err != nil {
|
||||
s.HandleError(c, http.StatusInternalServerError, "WireGuard error", err.Error())
|
||||
return
|
||||
}
|
||||
peers, err := s.wg.GetPeerList()
|
||||
if err != nil {
|
||||
s.HandleError(c, http.StatusInternalServerError, "WireGuard error", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
users := make([]User, len(peers))
|
||||
for i, peer := range peers {
|
||||
users[i] = s.users.GetOrCreateUserForPeer(peer)
|
||||
}
|
||||
c.HTML(http.StatusOK, "admin_index.html", gin.H{
|
||||
"route": c.Request.URL.Path,
|
||||
"session": s.getSessionData(c),
|
||||
"static": s.getStaticData(),
|
||||
"peers": users,
|
||||
"interface": dev,
|
||||
})
|
||||
}
|
96
internal/server/handlers_auth.go
Normal file
96
internal/server/handlers_auth.go
Normal file
@@ -0,0 +1,96 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func (s *Server) GetLogin(c *gin.Context) {
|
||||
currentSession := s.getSessionData(c)
|
||||
if currentSession.LoggedIn {
|
||||
c.Redirect(http.StatusSeeOther, "/") // already logged in
|
||||
}
|
||||
|
||||
authError := c.DefaultQuery("err", "")
|
||||
errMsg := "Unknown error occurred, try again!"
|
||||
switch authError {
|
||||
case "missingdata":
|
||||
errMsg = "Invalid login data retrieved, please fill out all fields and try again!"
|
||||
case "authfail":
|
||||
errMsg = "Authentication failed!"
|
||||
case "loginreq":
|
||||
errMsg = "Login required!"
|
||||
}
|
||||
|
||||
c.HTML(http.StatusOK, "login.html", gin.H{
|
||||
"error": authError != "",
|
||||
"message": errMsg,
|
||||
"static": s.getStaticData(),
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Server) PostLogin(c *gin.Context) {
|
||||
currentSession := s.getSessionData(c)
|
||||
if currentSession.LoggedIn {
|
||||
// already logged in
|
||||
c.Redirect(http.StatusSeeOther, "/")
|
||||
return
|
||||
}
|
||||
|
||||
username := strings.ToLower(c.PostForm("username"))
|
||||
password := c.PostForm("password")
|
||||
|
||||
// Validate form input
|
||||
if strings.Trim(username, " ") == "" || strings.Trim(password, " ") == "" {
|
||||
c.Redirect(http.StatusSeeOther, s.config.AuthRoutePrefix+"/login?err=missingdata")
|
||||
return
|
||||
}
|
||||
|
||||
// Check if user is in cache, avoid unnecessary ldap requests
|
||||
if !s.ldapUsers.UserExists(username) {
|
||||
c.Redirect(http.StatusSeeOther, s.config.AuthRoutePrefix+"/login?err=authfail")
|
||||
}
|
||||
|
||||
// Check if username and password match
|
||||
if !s.ldapAuth.CheckLogin(username, password) {
|
||||
c.Redirect(http.StatusSeeOther, s.config.AuthRoutePrefix+"/login?err=authfail")
|
||||
return
|
||||
}
|
||||
|
||||
dn := s.ldapUsers.GetUserDN(username)
|
||||
userData := s.ldapUsers.GetUserData(dn)
|
||||
sessionData := SessionData{
|
||||
LoggedIn: true,
|
||||
IsAdmin: s.ldapUsers.IsInGroup(username, s.config.AdminLdapGroup),
|
||||
UID: userData.GetUID(),
|
||||
UserName: username,
|
||||
Firstname: userData.Firstname,
|
||||
Lastname: userData.Lastname,
|
||||
SortedBy: "sn",
|
||||
SortDirection: "asc",
|
||||
Search: "",
|
||||
}
|
||||
|
||||
if err := s.updateSessionData(c, sessionData); err != nil {
|
||||
s.HandleError(c, http.StatusInternalServerError, "login error", "failed to save session")
|
||||
return
|
||||
}
|
||||
c.Redirect(http.StatusSeeOther, "/")
|
||||
}
|
||||
|
||||
func (s *Server) GetLogout(c *gin.Context) {
|
||||
currentSession := s.getSessionData(c)
|
||||
|
||||
if !currentSession.LoggedIn { // Not logged in
|
||||
c.Redirect(http.StatusSeeOther, s.config.LogoutRedirectPath)
|
||||
return
|
||||
}
|
||||
|
||||
if err := s.destroySessionData(c); err != nil {
|
||||
s.HandleError(c, http.StatusInternalServerError, "logout error", "failed to destroy session")
|
||||
return
|
||||
}
|
||||
c.Redirect(http.StatusSeeOther, s.config.LogoutRedirectPath)
|
||||
}
|
51
internal/server/routes.go
Normal file
51
internal/server/routes.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func SetupRoutes(s *Server) {
|
||||
// Startpage
|
||||
s.server.GET("/", s.GetIndex)
|
||||
|
||||
// Auth routes
|
||||
auth := s.server.Group("/auth")
|
||||
auth.GET("/login", s.GetLogin)
|
||||
auth.POST("/login", s.PostLogin)
|
||||
auth.GET("/logout", s.GetLogout)
|
||||
|
||||
// Admin routes
|
||||
admin := s.server.Group("/admin")
|
||||
admin.Use(s.RequireAuthentication(s.config.AdminLdapGroup))
|
||||
admin.GET("/", s.GetAdminIndex)
|
||||
|
||||
// User routes
|
||||
user := s.server.Group("/user")
|
||||
user.Use(s.RequireAuthentication("")) // empty scope = all logged in users
|
||||
}
|
||||
|
||||
func (s *Server) RequireAuthentication(scope string) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
session := s.getSessionData(c)
|
||||
|
||||
if !session.LoggedIn {
|
||||
// Abort the request with the appropriate error code
|
||||
c.Abort()
|
||||
c.Redirect(http.StatusSeeOther, s.config.AuthRoutePrefix+"/login?err=loginreq")
|
||||
return
|
||||
}
|
||||
|
||||
if scope != "" && !s.ldapUsers.IsInGroup(session.UserName, s.config.AdminLdapGroup) && // admins always have access
|
||||
!s.ldapUsers.IsInGroup(session.UserName, scope) {
|
||||
// Abort the request with the appropriate error code
|
||||
c.Abort()
|
||||
s.HandleError(c, http.StatusUnauthorized, "unauthorized", "not enough permissions")
|
||||
return
|
||||
}
|
||||
|
||||
// Continue down the chain to handler etc
|
||||
c.Next()
|
||||
}
|
||||
}
|
322
internal/server/usermanager.go
Normal file
322
internal/server/usermanager.go
Normal file
@@ -0,0 +1,322 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/h44z/wg-portal/internal/common"
|
||||
|
||||
"github.com/h44z/wg-portal/internal/ldap"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
Peer wgtypes.Peer `gorm:"-"`
|
||||
User *ldap.UserCacheHolderEntry `gorm:"-"` // optional, it is still possible to have users without ldap
|
||||
|
||||
UID string // uid for html identification
|
||||
IsOnline bool `gorm:"-"`
|
||||
Identifier string // Identifier AND Email make a WireGuard peer unique
|
||||
Email string `gorm:"index"`
|
||||
|
||||
IgnorePersistentKeepalive bool
|
||||
PresharedKey string
|
||||
AllowedIPsStr string
|
||||
IPsStr string
|
||||
AllowedIPs []string `gorm:"-"` // IPs that are used in the client config file
|
||||
IPs []string `gorm:"-"` // The IPs of the client
|
||||
PrivateKey string
|
||||
PublicKey string `gorm:"primaryKey"`
|
||||
|
||||
DeactivatedAt *time.Time
|
||||
CreatedBy string
|
||||
UpdatedBy string
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
func (u *User) GetPeerConfig() wgtypes.PeerConfig {
|
||||
publicKey, _ := wgtypes.ParseKey(u.PublicKey)
|
||||
var presharedKey *wgtypes.Key
|
||||
if u.PresharedKey != "" {
|
||||
presharedKeyTmp, _ := wgtypes.ParseKey(u.PresharedKey)
|
||||
presharedKey = &presharedKeyTmp
|
||||
}
|
||||
|
||||
cfg := wgtypes.PeerConfig{
|
||||
PublicKey: publicKey,
|
||||
Remove: false,
|
||||
UpdateOnly: false,
|
||||
PresharedKey: presharedKey,
|
||||
Endpoint: nil,
|
||||
PersistentKeepaliveInterval: nil,
|
||||
ReplaceAllowedIPs: true,
|
||||
AllowedIPs: make([]net.IPNet, len(u.IPs)),
|
||||
}
|
||||
for i, ip := range u.IPs {
|
||||
_, ipNet, err := net.ParseCIDR(ip)
|
||||
if err == nil {
|
||||
cfg.AllowedIPs[i] = *ipNet
|
||||
}
|
||||
}
|
||||
|
||||
return cfg
|
||||
}
|
||||
|
||||
type Device struct {
|
||||
DeviceName string `gorm:"primaryKey"`
|
||||
PrivateKey string
|
||||
PublicKey string
|
||||
PersistentKeepalive int
|
||||
ListenPort int
|
||||
Mtu int
|
||||
Endpoint string
|
||||
AllowedIPsStr string
|
||||
IPsStr string
|
||||
AllowedIPs []string `gorm:"-"` // IPs that are used in the client config file
|
||||
IPs []string `gorm:"-"` // The IPs of the client
|
||||
DNSStr string
|
||||
DNS []string `gorm:"-"` // The DNS servers of the client
|
||||
PreUp string
|
||||
PostUp string
|
||||
PreDown string
|
||||
PostDown string
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
func (d *Device) IsValid() bool {
|
||||
if len(d.IPs) == 0 {
|
||||
return false
|
||||
}
|
||||
if d.Endpoint == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
type UserManager struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewUserManager() *UserManager {
|
||||
um := &UserManager{}
|
||||
var err error
|
||||
um.db, err = gorm.Open(sqlite.Open("wg_portal.db"), &gorm.Config{})
|
||||
if err != nil {
|
||||
log.Errorf("failed to open sqlite database: %v", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
err = um.db.AutoMigrate(&User{}, &Device{})
|
||||
if err != nil {
|
||||
log.Errorf("failed to migrate sqlite database: %v", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
return um
|
||||
}
|
||||
|
||||
func (u *UserManager) InitWithPeers(peers []wgtypes.Peer, err error) {
|
||||
if err != nil {
|
||||
log.Errorf("failed to init user-manager from peers: %v", err)
|
||||
return
|
||||
}
|
||||
for _, peer := range peers {
|
||||
u.GetOrCreateUserForPeer(peer)
|
||||
}
|
||||
}
|
||||
|
||||
func (u *UserManager) InitWithDevice(dev *wgtypes.Device, err error) {
|
||||
if err != nil {
|
||||
log.Errorf("failed to init user-manager from device: %v", err)
|
||||
return
|
||||
}
|
||||
u.GetOrCreateDevice(*dev)
|
||||
}
|
||||
|
||||
func (u *UserManager) GetAllUsers() []User {
|
||||
users := make([]User, 0)
|
||||
u.db.Find(&users)
|
||||
|
||||
for i := range users {
|
||||
users[i].AllowedIPs = strings.Split(users[i].AllowedIPsStr, ", ")
|
||||
users[i].IPs = strings.Split(users[i].IPsStr, ", ")
|
||||
}
|
||||
|
||||
return users
|
||||
}
|
||||
|
||||
func (u *UserManager) GetAllDevices() []Device {
|
||||
devices := make([]Device, 0)
|
||||
u.db.Find(&devices)
|
||||
|
||||
for i := range devices {
|
||||
devices[i].AllowedIPs = strings.Split(devices[i].AllowedIPsStr, ", ")
|
||||
devices[i].IPs = strings.Split(devices[i].IPsStr, ", ")
|
||||
devices[i].DNS = strings.Split(devices[i].DNSStr, ", ")
|
||||
}
|
||||
|
||||
return devices
|
||||
}
|
||||
|
||||
func (u *UserManager) GetOrCreateUserForPeer(peer wgtypes.Peer) User {
|
||||
user := User{}
|
||||
u.db.Where("public_key = ?", peer.PublicKey.String()).FirstOrInit(&user)
|
||||
|
||||
if user.PublicKey == "" { // user not found, create
|
||||
user.UID = fmt.Sprintf("u%x", md5.Sum([]byte(peer.PublicKey.String())))
|
||||
user.PublicKey = peer.PublicKey.String()
|
||||
user.PrivateKey = "" // UNKNOWN
|
||||
if peer.PresharedKey != (wgtypes.Key{}) {
|
||||
user.PresharedKey = peer.PresharedKey.String()
|
||||
}
|
||||
user.Email = "autodetected@example.com"
|
||||
user.Identifier = "Autodetected (" + user.PublicKey[0:8] + ")"
|
||||
user.UpdatedAt = time.Now()
|
||||
user.CreatedAt = time.Now()
|
||||
user.AllowedIPs = make([]string, 0) // UNKNOWN
|
||||
user.IPs = make([]string, len(peer.AllowedIPs))
|
||||
for i, ip := range peer.AllowedIPs {
|
||||
user.IPs[i] = ip.String()
|
||||
}
|
||||
user.AllowedIPsStr = strings.Join(user.AllowedIPs, ", ")
|
||||
user.IPsStr = strings.Join(user.IPs, ", ")
|
||||
|
||||
res := u.db.Create(&user)
|
||||
if res.Error != nil {
|
||||
log.Errorf("failed to create autodetected peer: %v", res.Error)
|
||||
}
|
||||
}
|
||||
|
||||
user.IPs = strings.Split(user.IPsStr, ", ")
|
||||
user.AllowedIPs = strings.Split(user.AllowedIPsStr, ", ")
|
||||
|
||||
return user
|
||||
}
|
||||
|
||||
func (u *UserManager) CreateUser(user User) error {
|
||||
user.UID = fmt.Sprintf("u%x", md5.Sum([]byte(user.PublicKey)))
|
||||
user.UpdatedAt = time.Now()
|
||||
user.CreatedAt = time.Now()
|
||||
user.AllowedIPsStr = strings.Join(user.AllowedIPs, ", ")
|
||||
user.IPsStr = strings.Join(user.IPs, ", ")
|
||||
|
||||
res := u.db.Create(&user)
|
||||
if res.Error != nil {
|
||||
log.Errorf("failed to create user: %v", res.Error)
|
||||
return res.Error
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UserManager) UpdateUser(user User) error {
|
||||
user.UpdatedAt = time.Now()
|
||||
user.AllowedIPsStr = strings.Join(user.AllowedIPs, ", ")
|
||||
user.IPsStr = strings.Join(user.IPs, ", ")
|
||||
|
||||
res := u.db.Save(&user)
|
||||
if res.Error != nil {
|
||||
log.Errorf("failed to update user: %v", res.Error)
|
||||
return res.Error
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UserManager) GetAllReservedIps() ([]string, error) {
|
||||
reservedIps := make([]string, 0)
|
||||
users := u.GetAllUsers()
|
||||
for _, user := range users {
|
||||
for _, cidr := range user.IPs {
|
||||
ip, _, err := net.ParseCIDR(cidr)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"err": err,
|
||||
"cidr": cidr,
|
||||
}).Error("failed to ip from cidr")
|
||||
} else {
|
||||
reservedIps = append(reservedIps, ip.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
devices := u.GetAllDevices()
|
||||
for _, device := range devices {
|
||||
for _, cidr := range device.IPs {
|
||||
ip, _, err := net.ParseCIDR(cidr)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"err": err,
|
||||
"cidr": cidr,
|
||||
}).Error("failed to ip from cidr")
|
||||
} else {
|
||||
reservedIps = append(reservedIps, ip.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return reservedIps, nil
|
||||
}
|
||||
|
||||
// GetAvailableIp search for an available ip in cidr against a list of reserved ips
|
||||
func (u *UserManager) GetAvailableIp(cidr string, reserved []string) (string, error) {
|
||||
ip, ipnet, err := net.ParseCIDR(cidr)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// this two addresses are not usable
|
||||
broadcastAddr := common.BroadcastAddr(ipnet).String()
|
||||
networkAddr := ipnet.IP.String()
|
||||
|
||||
for ip := ip.Mask(ipnet.Mask); ipnet.Contains(ip); common.IncreaseIP(ip) {
|
||||
ok := true
|
||||
address := ip.String()
|
||||
for _, r := range reserved {
|
||||
if address == r {
|
||||
ok = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if ok && address != networkAddr && address != broadcastAddr {
|
||||
return address, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", errors.New("no more available address from cidr")
|
||||
}
|
||||
|
||||
func (u *UserManager) GetOrCreateDevice(dev wgtypes.Device) Device {
|
||||
device := Device{}
|
||||
u.db.Where("device_name = ?", dev.Name).FirstOrInit(&device)
|
||||
|
||||
if device.PublicKey == "" { // device not found, create
|
||||
device.PublicKey = dev.PublicKey.String()
|
||||
device.PrivateKey = dev.PrivateKey.String()
|
||||
device.DeviceName = dev.Name
|
||||
device.ListenPort = dev.ListenPort
|
||||
device.Mtu = 0
|
||||
device.PersistentKeepalive = 16 // Default
|
||||
|
||||
res := u.db.Create(&device)
|
||||
if res.Error != nil {
|
||||
log.Errorf("failed to create autodetected device: %v", res.Error)
|
||||
}
|
||||
}
|
||||
|
||||
device.IPs = strings.Split(device.IPsStr, ", ")
|
||||
device.AllowedIPs = strings.Split(device.AllowedIPsStr, ", ")
|
||||
device.DNS = strings.Split(device.DNSStr, ", ")
|
||||
|
||||
return device
|
||||
}
|
Reference in New Issue
Block a user