From 5a933f20c91e4dc1d8cfa77d52c661382eece137 Mon Sep 17 00:00:00 2001 From: Christoph Haas Date: Mon, 9 Nov 2020 11:06:02 +0100 Subject: [PATCH] wip: create/update/... --- assets/tpl/admin_create_clients.html | 6 +- assets/tpl/admin_edit_client.html | 2 +- assets/tpl/admin_index.html | 6 +- assets/tpl/index.html | 4 +- internal/server/core.go | 3 + internal/server/handlers.go | 286 ++++++++++++++------------- internal/server/helper.go | 149 ++++++++++++++ internal/server/usermanager.go | 95 +++++++-- 8 files changed, 391 insertions(+), 160 deletions(-) create mode 100644 internal/server/helper.go diff --git a/assets/tpl/admin_create_clients.html b/assets/tpl/admin_create_clients.html index 12dd8a9..63585c9 100644 --- a/assets/tpl/admin_create_clients.html +++ b/assets/tpl/admin_create_clients.html @@ -18,7 +18,7 @@ {{template "prt_nav.html" .}}

Create new clients

- +

Enter valid LDAP user email addresses to quickly create new accounts.

{{if $.Alerts.HasAlert}}
@@ -35,13 +35,13 @@
- +
- +
diff --git a/assets/tpl/admin_edit_client.html b/assets/tpl/admin_edit_client.html index d8ba9f4..8fdfebd 100644 --- a/assets/tpl/admin_edit_client.html +++ b/assets/tpl/admin_edit_client.html @@ -14,7 +14,7 @@ {{template "prt_nav.html" .}}
- {{if eq .Peer.UID ""}} + {{if .Peer.IsNew}}

Create a new client

{{else}}

Edit client {{.Peer.Identifier}}

diff --git a/assets/tpl/admin_index.html b/assets/tpl/admin_index.html index b76694d..23600f9 100644 --- a/assets/tpl/admin_index.html +++ b/assets/tpl/admin_index.html @@ -137,7 +137,7 @@ Configuration
@@ -158,9 +158,7 @@
{{$p.Config}}
-
    -
  • 2
  • -
+ Delete
diff --git a/assets/tpl/index.html b/assets/tpl/index.html index 749fbd5..fe13534 100644 --- a/assets/tpl/index.html +++ b/assets/tpl/index.html @@ -5,8 +5,8 @@ - {{ .static.WebsiteTitle }} - + {{ .Static.WebsiteTitle }} + diff --git a/internal/server/core.go b/internal/server/core.go index 063ad53..c55e090 100644 --- a/internal/server/core.go +++ b/internal/server/core.go @@ -24,6 +24,9 @@ const CacheRefreshDuration = 5 * time.Minute func init() { gob.Register(SessionData{}) + gob.Register(User{}) + gob.Register(Device{}) + gob.Register(LdapCreateForm{}) } type SessionData struct { diff --git a/internal/server/handlers.go b/internal/server/handlers.go index 1a9ad14..3951de1 100644 --- a/internal/server/handlers.go +++ b/internal/server/handlers.go @@ -1,8 +1,6 @@ package server import ( - "crypto/md5" - "fmt" "net/http" "net/url" "strconv" @@ -15,16 +13,27 @@ import ( "github.com/h44z/wg-portal/internal/common" - "golang.zx2c4.com/wireguard/wgctrl/wgtypes" - "github.com/gin-gonic/gin" ) +type LdapCreateForm struct { + Emails string `form:"email" binding:"required"` + Identifier string `form:"identifier" binding:"required,lte=20"` +} + 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(), + c.HTML(http.StatusOK, "index.html", struct { + Route string + Alerts AlertData + Session SessionData + Static StaticData + Device Device + }{ + Route: c.Request.URL.Path, + Alerts: s.getAlertData(c), + Session: s.getSessionData(c), + Static: s.getStaticData(), + Device: s.users.GetDevice(), }) } @@ -67,6 +76,12 @@ func (s *Server) GetAdminEditInterface(c *gin.Context) { device := s.users.GetDevice() users := s.users.GetAllUsers() + currentSession, err := s.setFormInSession(c, device) + if err != nil { + s.HandleError(c, http.StatusInternalServerError, "Session error", err.Error()) + return + } + c.HTML(http.StatusOK, "admin_edit_interface.html", struct { Route string Alerts AlertData @@ -77,18 +92,23 @@ func (s *Server) GetAdminEditInterface(c *gin.Context) { }{ Route: c.Request.URL.Path, Alerts: s.getAlertData(c), - Session: s.getSessionData(c), + Session: currentSession, Static: s.getStaticData(), Peers: users, - Device: device, + Device: currentSession.FormData.(Device), }) } func (s *Server) PostAdminEditInterface(c *gin.Context) { + currentSession := s.getSessionData(c) var formDevice Device + if currentSession.FormData != nil { + formDevice = currentSession.FormData.(Device) + } if err := c.ShouldBind(&formDevice); err != nil { + _ = s.updateFormInSession(c, formDevice) s.setAlert(c, "failed to bind form data: "+err.Error(), "danger") - c.Redirect(http.StatusSeeOther, "/admin/device/edit") + c.Redirect(http.StatusSeeOther, "/admin/device/edit?formerr=bind") return } // Clean list input @@ -102,16 +122,18 @@ func (s *Server) PostAdminEditInterface(c *gin.Context) { // Update WireGuard device err := s.wg.UpdateDevice(formDevice.DeviceName, formDevice.GetDeviceConfig()) if err != nil { + _ = s.updateFormInSession(c, formDevice) s.setAlert(c, "failed to update device in WireGuard: "+err.Error(), "danger") - c.Redirect(http.StatusSeeOther, "/admin/device/edit") + c.Redirect(http.StatusSeeOther, "/admin/device/edit?formerr=wg") return } // Update in database err = s.users.UpdateDevice(formDevice) if err != nil { + _ = s.updateFormInSession(c, formDevice) s.setAlert(c, "failed to update device in database: "+err.Error(), "danger") - c.Redirect(http.StatusSeeOther, "/admin/device/edit") + c.Redirect(http.StatusSeeOther, "/admin/device/edit?formerr=update") return } @@ -123,6 +145,12 @@ func (s *Server) GetAdminEditPeer(c *gin.Context) { device := s.users.GetDevice() user := s.users.GetUserByKey(c.Query("pkey")) + currentSession, err := s.setFormInSession(c, user) + if err != nil { + s.HandleError(c, http.StatusInternalServerError, "Session error", err.Error()) + return + } + c.HTML(http.StatusOK, "admin_edit_client.html", struct { Route string Alerts AlertData @@ -133,9 +161,9 @@ func (s *Server) GetAdminEditPeer(c *gin.Context) { }{ Route: c.Request.URL.Path, Alerts: s.getAlertData(c), - Session: s.getSessionData(c), + Session: currentSession, Static: s.getStaticData(), - Peer: user, + Peer: currentSession.FormData.(User), Device: device, }) } @@ -144,10 +172,15 @@ func (s *Server) PostAdminEditPeer(c *gin.Context) { currentUser := s.users.GetUserByKey(c.Query("pkey")) urlEncodedKey := url.QueryEscape(c.Query("pkey")) + currentSession := s.getSessionData(c) var formUser User + if currentSession.FormData != nil { + formUser = currentSession.FormData.(User) + } if err := c.ShouldBind(&formUser); err != nil { + _ = s.updateFormInSession(c, formUser) s.setAlert(c, "failed to bind form data: "+err.Error(), "danger") - c.Redirect(http.StatusSeeOther, "/admin/peer/edit?pkey="+urlEncodedKey) + c.Redirect(http.StatusSeeOther, "/admin/peer/edit?pkey="+urlEncodedKey+"&formerr=bind") return } @@ -165,35 +198,11 @@ func (s *Server) PostAdminEditPeer(c *gin.Context) { formUser.DeactivatedAt = nil } - // Update WireGuard device - if formUser.DeactivatedAt == &now { - err := s.wg.RemovePeer(formUser.PublicKey) - if err != nil { - s.setAlert(c, "failed to remove peer in WireGuard: "+err.Error(), "danger") - c.Redirect(http.StatusSeeOther, "/admin/peer/edit?pkey="+urlEncodedKey) - return - } - } else if formUser.DeactivatedAt == nil && currentUser.Peer != nil { - err := s.wg.UpdatePeer(formUser.GetPeerConfig()) - if err != nil { - s.setAlert(c, "failed to update peer in WireGuard: "+err.Error(), "danger") - c.Redirect(http.StatusSeeOther, "/admin/peer/edit?pkey="+urlEncodedKey) - return - } - } else if formUser.DeactivatedAt == nil && currentUser.Peer == nil { - err := s.wg.AddPeer(formUser.GetPeerConfig()) - if err != nil { - s.setAlert(c, "failed to add peer in WireGuard: "+err.Error(), "danger") - c.Redirect(http.StatusSeeOther, "/admin/peer/edit?pkey="+urlEncodedKey) - return - } - } - // Update in database - err := s.users.UpdateUser(formUser) - if err != nil { - s.setAlert(c, "failed to update user in database: "+err.Error(), "danger") - c.Redirect(http.StatusSeeOther, "/admin/peer/edit?pkey="+urlEncodedKey) + if err := s.UpdateUser(formUser, now); err != nil { + _ = s.updateFormInSession(c, formUser) + s.setAlert(c, "failed to update user: "+err.Error(), "danger") + c.Redirect(http.StatusSeeOther, "/admin/peer/edit?pkey="+urlEncodedKey+"&formerr=update") return } @@ -203,24 +212,12 @@ func (s *Server) PostAdminEditPeer(c *gin.Context) { func (s *Server) GetAdminCreatePeer(c *gin.Context) { device := s.users.GetDevice() - user := User{} - user.AllowedIPsStr = device.AllowedIPsStr - user.IPsStr = "" // TODO: add a valid ip here - psk, err := wgtypes.GenerateKey() - if err != nil { - s.HandleError(c, http.StatusInternalServerError, "Preshared key generation error", err.Error()) - return - } - key, err := wgtypes.GeneratePrivateKey() - if err != nil { - s.HandleError(c, http.StatusInternalServerError, "Private key generation error", err.Error()) - return - } - user.PresharedKey = psk.String() - user.PrivateKey = key.String() - user.PublicKey = key.PublicKey().String() - user.UID = fmt.Sprintf("u%x", md5.Sum([]byte(user.PublicKey))) + currentSession, err := s.setNewUserFormInSession(c) + if err != nil { + s.HandleError(c, http.StatusInternalServerError, "Session error", err.Error()) + return + } c.HTML(http.StatusOK, "admin_edit_client.html", struct { Route string Alerts AlertData @@ -231,18 +228,23 @@ func (s *Server) GetAdminCreatePeer(c *gin.Context) { }{ Route: c.Request.URL.Path, Alerts: s.getAlertData(c), - Session: s.getSessionData(c), + Session: currentSession, Static: s.getStaticData(), - Peer: user, + Peer: currentSession.FormData.(User), Device: device, }) } func (s *Server) PostAdminCreatePeer(c *gin.Context) { + currentSession := s.getSessionData(c) var formUser User + if currentSession.FormData != nil { + formUser = currentSession.FormData.(User) + } if err := c.ShouldBind(&formUser); err != nil { + _ = s.updateFormInSession(c, formUser) s.setAlert(c, "failed to bind form data: "+err.Error(), "danger") - c.Redirect(http.StatusSeeOther, "/admin/peer/create") + c.Redirect(http.StatusSeeOther, "/admin/peer/create?formerr=bind") return } @@ -258,21 +260,10 @@ func (s *Server) PostAdminCreatePeer(c *gin.Context) { formUser.DeactivatedAt = &now } - // Update WireGuard device - if formUser.DeactivatedAt == nil { - err := s.wg.AddPeer(formUser.GetPeerConfig()) - if err != nil { - s.setAlert(c, "failed to add peer in WireGuard: "+err.Error(), "danger") - c.Redirect(http.StatusSeeOther, "/admin/peer/create") - return - } - } - - // Create in database - err := s.users.CreateUser(formUser) - if err != nil { - s.setAlert(c, "failed to add user in database: "+err.Error(), "danger") - c.Redirect(http.StatusSeeOther, "/admin/peer/create") + if err := s.CreateUser(formUser); err != nil { + _ = s.updateFormInSession(c, formUser) + s.setAlert(c, "failed to add user: "+err.Error(), "danger") + c.Redirect(http.StatusSeeOther, "/admin/peer/create?formerr=create") return } @@ -281,84 +272,62 @@ func (s *Server) PostAdminCreatePeer(c *gin.Context) { } func (s *Server) GetAdminCreateLdapPeers(c *gin.Context) { - device := s.users.GetDevice() + currentSession, err := s.setFormInSession(c, LdapCreateForm{Identifier: "Default"}) + if err != nil { + s.HandleError(c, http.StatusInternalServerError, "Session error", err.Error()) + return + } c.HTML(http.StatusOK, "admin_create_clients.html", struct { - Route string - Alerts AlertData - Session SessionData - Static StaticData - Users []*ldap.UserCacheHolderEntry - Device Device + Route string + Alerts AlertData + Session SessionData + Static StaticData + Users []*ldap.UserCacheHolderEntry + FormData LdapCreateForm + Device Device }{ - Route: c.Request.URL.Path, - Alerts: s.getAlertData(c), - Session: s.getSessionData(c), - Static: s.getStaticData(), - Users: s.ldapUsers.GetSortedUsers("sn", "asc"), - Device: device, + Route: c.Request.URL.Path, + Alerts: s.getAlertData(c), + Session: currentSession, + Static: s.getStaticData(), + Users: s.ldapUsers.GetSortedUsers("sn", "asc"), + FormData: currentSession.FormData.(LdapCreateForm), + Device: s.users.GetDevice(), }) } func (s *Server) PostAdminCreateLdapPeers(c *gin.Context) { - email := c.PostForm("email") - identifier := c.PostForm("identifier") - if identifier == "" { - identifier = "Default" + currentSession := s.getSessionData(c) + var formData LdapCreateForm + if currentSession.FormData != nil { + formData = currentSession.FormData.(LdapCreateForm) } - if email == "" { - s.setAlert(c, "missing email address", "danger") - c.Redirect(http.StatusSeeOther, "/admin/peer/createldap") + if err := c.ShouldBind(&formData); err != nil { + _ = s.updateFormInSession(c, formData) + s.setAlert(c, "failed to bind form data: "+err.Error(), "danger") + c.Redirect(http.StatusSeeOther, "/admin/peer/createldap?formerr=bind") return } - emails := common.ParseStringList(email) + + emails := common.ParseStringList(formData.Emails) for i := range emails { // TODO: also check email addr for validity? if !strings.ContainsRune(emails[i], '@') || s.ldapUsers.GetUserDNByMail(emails[i]) == "" { + _ = s.updateFormInSession(c, formData) s.setAlert(c, "invalid email address: "+emails[i], "danger") - c.Redirect(http.StatusSeeOther, "/admin/peer/createldap") + c.Redirect(http.StatusSeeOther, "/admin/peer/createldap?formerr=mail") return } } log.Infof("creating %d ldap peers", len(emails)) - device := s.users.GetDevice() for i := range emails { - ldapUser := s.ldapUsers.GetUserData(s.ldapUsers.GetUserDNByMail(emails[i])) - user := User{} - user.AllowedIPsStr = device.AllowedIPsStr - user.IPsStr = "" // TODO: add a valid ip here - psk, err := wgtypes.GenerateKey() - if err != nil { - s.HandleError(c, http.StatusInternalServerError, "Preshared key generation error", err.Error()) - return - } - key, err := wgtypes.GeneratePrivateKey() - if err != nil { - s.HandleError(c, http.StatusInternalServerError, "Private key generation error", err.Error()) - return - } - user.PresharedKey = psk.String() - user.PrivateKey = key.String() - user.PublicKey = key.PublicKey().String() - user.UID = fmt.Sprintf("u%x", md5.Sum([]byte(user.PublicKey))) - user.Email = emails[i] - user.Identifier = fmt.Sprintf("%s %s (%s)", ldapUser.Firstname, ldapUser.Lastname, identifier) - - // Create wireguard interface - err = s.wg.AddPeer(user.GetPeerConfig()) - if err != nil { - s.setAlert(c, "failed to add peer in WireGuard: "+err.Error(), "danger") - c.Redirect(http.StatusSeeOther, "/admin/peer/createldap") - return - } - - // Create in database - err = s.users.CreateUser(user) - if err != nil { - s.setAlert(c, "failed to add user in database: "+err.Error(), "danger") - c.Redirect(http.StatusSeeOther, "/admin/peer/createldap") + if err := s.CreateUserByEmail(emails[i], formData.Identifier, false); err != nil { + _ = s.updateFormInSession(c, formData) + s.setAlert(c, "failed to add user: "+err.Error(), "danger") + c.Redirect(http.StatusSeeOther, "/admin/peer/createldap?formerr=create") return } } @@ -377,3 +346,48 @@ func (s *Server) GetUserQRCode(c *gin.Context) { c.Data(http.StatusOK, "image/png", png) return } + +func (s *Server) updateFormInSession(c *gin.Context, formData interface{}) error { + currentSession := s.getSessionData(c) + currentSession.FormData = formData + + if err := s.updateSessionData(c, currentSession); err != nil { + return err + } + + return nil +} + +func (s *Server) setNewUserFormInSession(c *gin.Context) (SessionData, error) { + currentSession := s.getSessionData(c) + // If session does not contain a user form ignore update + // If url contains a formerr parameter reset the form + if currentSession.FormData == nil || c.Query("formerr") == "" { + user, err := s.PrepareNewUser() + if err != nil { + return currentSession, err + } + currentSession.FormData = user + } + + if err := s.updateSessionData(c, currentSession); err != nil { + return currentSession, err + } + + return currentSession, nil +} + +func (s *Server) setFormInSession(c *gin.Context, formData interface{}) (SessionData, error) { + currentSession := s.getSessionData(c) + // If session does not contain a form ignore update + // If url contains a formerr parameter reset the form + if currentSession.FormData == nil || c.Query("formerr") == "" { + currentSession.FormData = formData + } + + if err := s.updateSessionData(c, currentSession); err != nil { + return currentSession, err + } + + return currentSession, nil +} diff --git a/internal/server/helper.go b/internal/server/helper.go new file mode 100644 index 0000000..ac45e62 --- /dev/null +++ b/internal/server/helper.go @@ -0,0 +1,149 @@ +package server + +import ( + "crypto/md5" + "errors" + "fmt" + "time" + + "github.com/h44z/wg-portal/internal/common" + + "golang.zx2c4.com/wireguard/wgctrl/wgtypes" +) + +func (s *Server) PrepareNewUser() (User, error) { + device := s.users.GetDevice() + + user := User{} + user.IsNew = true + user.AllowedIPsStr = device.AllowedIPsStr + user.IPs = make([]string, len(device.IPs)) + for i := range device.IPs { + freeIP, err := s.users.GetAvailableIp(device.IPs[i]) + if err != nil { + return User{}, err + } + user.IPs[i] = freeIP + } + user.IPsStr = common.ListToString(user.IPs) + psk, err := wgtypes.GenerateKey() + if err != nil { + return User{}, err + } + key, err := wgtypes.GeneratePrivateKey() + if err != nil { + return User{}, err + } + user.PresharedKey = psk.String() + user.PrivateKey = key.String() + user.PublicKey = key.PublicKey().String() + user.UID = fmt.Sprintf("u%x", md5.Sum([]byte(user.PublicKey))) + + return user, nil +} + +func (s *Server) CreateUserByEmail(email, identifierSuffix string, disabled bool) error { + ldapUser := s.ldapUsers.GetUserData(s.ldapUsers.GetUserDNByMail(email)) + if ldapUser.DN == "" { + return errors.New("no user with email " + email + " found") + } + + device := s.users.GetDevice() + user := User{} + user.AllowedIPsStr = device.AllowedIPsStr + user.IPsStr = "" // TODO: add a valid ip here + psk, err := wgtypes.GenerateKey() + if err != nil { + return err + } + key, err := wgtypes.GeneratePrivateKey() + if err != nil { + return err + } + user.PresharedKey = psk.String() + user.PrivateKey = key.String() + user.PublicKey = key.PublicKey().String() + user.UID = fmt.Sprintf("u%x", md5.Sum([]byte(user.PublicKey))) + user.Email = email + user.Identifier = fmt.Sprintf("%s %s (%s)", ldapUser.Firstname, ldapUser.Lastname, identifierSuffix) + now := time.Now() + if disabled { + user.DeactivatedAt = &now + } + + return s.CreateUser(user) +} + +func (s *Server) CreateUser(user User) error { + + device := s.users.GetDevice() + user.AllowedIPsStr = device.AllowedIPsStr + user.IPsStr = "" // TODO: add a valid ip here + if user.PrivateKey == "" { // if private key is empty create a new one + psk, err := wgtypes.GenerateKey() + if err != nil { + return err + } + key, err := wgtypes.GeneratePrivateKey() + if err != nil { + return err + } + user.PresharedKey = psk.String() + user.PrivateKey = key.String() + user.PublicKey = key.PublicKey().String() + } + user.UID = fmt.Sprintf("u%x", md5.Sum([]byte(user.PublicKey))) + + // Create wireguard interface + if user.DeactivatedAt == nil { + if err := s.wg.AddPeer(user.GetPeerConfig()); err != nil { + return err + } + } + + // Create in database + if err := s.users.CreateUser(user); err != nil { + return err + } + + return nil +} + +func (s *Server) UpdateUser(user User, updateTime time.Time) error { + currentUser := s.users.GetUserByKey(user.PublicKey) + + // Update WireGuard device + var err error + switch { + case user.DeactivatedAt == &updateTime: + err = s.wg.RemovePeer(user.PublicKey) + case user.DeactivatedAt == nil && currentUser.Peer != nil: + err = s.wg.UpdatePeer(user.GetPeerConfig()) + case user.DeactivatedAt == nil && currentUser.Peer == nil: + err = s.wg.AddPeer(user.GetPeerConfig()) + } + if err != nil { + return err + } + + // Update in database + if err := s.users.UpdateUser(user); err != nil { + return err + } + + return nil +} + +func (s *Server) DeleteUser(user User) error { + // Delete WireGuard peer + if err := s.wg.RemovePeer(user.PublicKey); err != nil { + return err + } + + // Delete in database + if err := s.users.DeleteUser(user); err != nil { + return err + } + + return nil +} diff --git a/internal/server/usermanager.go b/internal/server/usermanager.go index a9316e9..1af6f0f 100644 --- a/internal/server/usermanager.go +++ b/internal/server/usermanager.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "net" + "reflect" "strings" "text/template" "time" @@ -73,6 +74,7 @@ type User struct { UID string `form:"uid" binding:"alphanum"` // uid for html identification IsOnline bool `gorm:"-"` + IsNew bool `gorm:"-"` Identifier string `form:"identifier" binding:"required,lt=64"` // Identifier AND Email make a WireGuard peer unique Email string `gorm:"index" form:"mail" binding:"required,email"` @@ -161,6 +163,26 @@ func (u User) IsValid() bool { return true } +func (u User) ToMap() map[string]string { + out := make(map[string]string) + + v := reflect.ValueOf(u) + if v.Kind() == reflect.Ptr { + v = v.Elem() + } + + typ := v.Type() + for i := 0; i < v.NumField(); i++ { + // gets us a StructField + fi := typ.Field(i) + if tagv := fi.Tag.Get("form"); tagv != "" { + // set key of map to value in struct field + out[tagv] = v.Field(i).String() + } + } + return out +} + // // DEVICE -------------------------------------------------------------------------------------- // @@ -417,6 +439,16 @@ func (u *UserManager) UpdateUser(user User) error { return nil } +func (u *UserManager) DeleteUser(user User) error { + res := u.db.Delete(&user) + if res.Error != nil { + log.Errorf("failed to delete user: %v", res.Error) + return res.Error + } + + return nil +} + func (u *UserManager) UpdateDevice(device Device) error { device.UpdatedAt = time.Now() device.AllowedIPsStr = strings.Join(device.AllowedIPs, ", ") @@ -437,36 +469,67 @@ func (u *UserManager) GetAllReservedIps() ([]string, error) { users := u.GetAllUsers() for _, user := range users { for _, cidr := range user.IPs { + if cidr == "" { + continue + } 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 nil, err } + reservedIps = append(reservedIps, ip.String()) } } device := u.GetDevice() for _, cidr := range device.IPs { + if cidr == "" { + continue + } 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 nil, err } + + reservedIps = append(reservedIps, ip.String()) } return reservedIps, nil } +func (u *UserManager) IsIPReserved(cidr string) bool { + reserved, err := u.GetAllReservedIps() + if err != nil { + return true // in case something failed, assume the ip is reserved + } + ip, ipnet, err := net.ParseCIDR(cidr) + if err != nil { + return true + } + + // this two addresses are not usable + broadcastAddr := common.BroadcastAddr(ipnet).String() + networkAddr := ipnet.IP.String() + address := ip.String() + + if address == broadcastAddr || address == networkAddr { + return true + } + + for _, r := range reserved { + if address == r { + return true + } + } + + return false +} + // GetAvailableIp search for an available ip in cidr against a list of reserved ips -func (u *UserManager) GetAvailableIp(cidr string, reserved []string) (string, error) { +func (u *UserManager) GetAvailableIp(cidr string) (string, error) { + reserved, err := u.GetAllReservedIps() + if err != nil { + return "", err + } ip, ipnet, err := net.ParseCIDR(cidr) if err != nil { return "", err @@ -486,7 +549,11 @@ func (u *UserManager) GetAvailableIp(cidr string, reserved []string) (string, er } } if ok && address != networkAddr && address != broadcastAddr { - return address, nil + netMask := "/32" + if common.IsIPv6(address) { + netMask = "/128" + } + return address + netMask, nil } }