diff --git a/assets/tpl/admin_index.html b/assets/tpl/admin_index.html index c059ea0..0f1a9e6 100644 --- a/assets/tpl/admin_index.html +++ b/assets/tpl/admin_index.html @@ -150,16 +150,17 @@ {{end}} -

Traffic

+

Connection / Traffic

{{if not $p.Peer}}

No Traffic data available...

{{else}} -

{{if $p.DeactivatedAt}}-{{else}} {{formatBytes $p.Peer.ReceiveBytes}} / {{formatBytes $p.Peer.TransmitBytes}}{{end}}

+

{{if $p.DeactivatedAt}}-{{else}} {{formatBytes $p.Peer.Endpoint}}{{end}}

+

{{if $p.DeactivatedAt}}-{{else}} {{formatBytes $p.Peer.ReceiveBytes}} / {{formatBytes $p.Peer.TransmitBytes}}{{end}}

{{end}}
diff --git a/internal/common/configuration.go b/internal/common/configuration.go index 14e58c1..b4640f8 100644 --- a/internal/common/configuration.go +++ b/internal/common/configuration.go @@ -54,15 +54,17 @@ func loadConfigEnv(cfg interface{}) error { type Config struct { Core struct { - ListeningAddress string `yaml:"listeningAddress" envconfig:"LISTENING_ADDRESS"` - ExternalUrl string `yaml:"externalUrl" envconfig:"EXTERNAL_URL"` - Title string `yaml:"title" envconfig:"WEBSITE_TITLE"` - CompanyName string `yaml:"company" envconfig:"COMPANY_NAME"` - MailFrom string `yaml:"mailfrom" envconfig:"MAIL_FROM"` - AdminUser string `yaml:"adminUser" envconfig:"ADMIN_USER"` // optional, non LDAP admin user - AdminPassword string `yaml:"adminPass" envconfig:"ADMIN_PASS"` - DatabasePath string `yaml:"database" envconfig:"DATABASE_PATH"` - EditableKeys bool `yaml:"editableKeys" envconfig:"EDITABLE_KEYS"` + ListeningAddress string `yaml:"listeningAddress" envconfig:"LISTENING_ADDRESS"` + ExternalUrl string `yaml:"externalUrl" envconfig:"EXTERNAL_URL"` + Title string `yaml:"title" envconfig:"WEBSITE_TITLE"` + CompanyName string `yaml:"company" envconfig:"COMPANY_NAME"` + MailFrom string `yaml:"mailfrom" envconfig:"MAIL_FROM"` + AdminUser string `yaml:"adminUser" envconfig:"ADMIN_USER"` // optional, non LDAP admin user + AdminPassword string `yaml:"adminPass" envconfig:"ADMIN_PASS"` + DatabasePath string `yaml:"database" envconfig:"DATABASE_PATH"` + EditableKeys bool `yaml:"editableKeys" envconfig:"EDITABLE_KEYS"` + CreateInterfaceOnLogin bool `yaml:"createOnLogin" envconfig:"CREATE_INTERFACE_ON_LOGIN"` + SyncLdapStatus bool `yaml:"syncLdapStatus" envconfig:"SYNC_LDAP_STATUS"` // disable account if disabled in ldap } `yaml:"core"` Email MailConfig `yaml:"email"` LDAP ldap.Config `yaml:"ldap"` diff --git a/internal/ldap/authentication.go b/internal/ldap/authentication.go index 6122be2..d2952ed 100644 --- a/internal/ldap/authentication.go +++ b/internal/ldap/authentication.go @@ -63,7 +63,7 @@ func (a Authentication) CheckCustomLogin(userIdentifier, username, password stri a.Cfg.BaseDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, fmt.Sprintf("(&(objectClass=organizationalPerson)(%s=%s))", userIdentifier, username), - []string{"dn"}, + []string{"dn", "userAccountControl"}, nil, ) @@ -78,6 +78,12 @@ func (a Authentication) CheckCustomLogin(userIdentifier, username, password stri userDN := sr.Entries[0].DN + // Check if user is disabled, if so deny login + uac := sr.Entries[0].GetAttributeValue("userAccountControl") + if uac != "" && IsLdapUserDisabled(uac) { + return false + } + // Bind as the user to verify their password err = client.Bind(userDN, password) if err != nil { diff --git a/internal/ldap/usercache.go b/internal/ldap/usercache.go index 6fe5593..a56220d 100644 --- a/internal/ldap/usercache.go +++ b/internal/ldap/usercache.go @@ -5,6 +5,7 @@ import ( "crypto/tls" "fmt" "sort" + "strconv" "strings" "sync" "time" @@ -214,7 +215,7 @@ func NewUserCache(config Config, store UserCacheHolder) *UserCache { } log.Infof("Filling user cache...") - err := uc.Update(true) + err := uc.Update(true, true) log.Infof("User cache filled!") uc.LastError = err @@ -250,7 +251,7 @@ func (u UserCache) close(conn *ldap.Conn) { } // Update updates the user cache in background, minimal locking will happen -func (u *UserCache) Update(filter bool) error { +func (u *UserCache) Update(filter, withDisabledUsers bool) error { log.Debugf("Updating ldap cache...") client, err := u.open() if err != nil { @@ -290,8 +291,8 @@ func (u *UserCache) Update(filter bool) error { continue // prefilter... } - if userAccountControl == "" || userAccountControl == "514" { - continue // 514 means account is disabled + if !withDisabledUsers && userAccountControl != "" && IsLdapUserDisabled(userAccountControl) { + continue } if entry.DN != dn { @@ -323,3 +324,15 @@ func (u *UserCache) Update(filter bool) error { return nil } + +func IsLdapUserDisabled(userAccountControl string) bool { + uacInt, err := strconv.Atoi(userAccountControl) + if err != nil { + return true + } + if int32(uacInt)&0x2 != 0 { + return true // bit 2 set means account is disabled + } + + return false +} diff --git a/internal/server/core.go b/internal/server/core.go index f350786..747dc99 100644 --- a/internal/server/core.go +++ b/internal/server/core.go @@ -157,7 +157,7 @@ func (s *Server) Run() { go func(s *Server) { for { time.Sleep(CacheRefreshDuration) - if err := s.ldapCacheUpdater.Update(true); err != nil { + if err := s.ldapCacheUpdater.Update(true, true); err != nil { log.Warnf("Failed to update ldap group cache: %v", err) } log.Debugf("Refreshed LDAP permissions!") @@ -165,6 +165,18 @@ func (s *Server) Run() { }(s) } + if !s.ldapDisabled && s.config.Core.SyncLdapStatus { + go func(s *Server) { + for { + time.Sleep(CacheRefreshDuration) + if err := s.SyncLdapAttributesWithWireGuard(); err != nil { + log.Warnf("Failed to synchronize ldap attributes: %v", err) + } + log.Debugf("Synced LDAP attributes!") + } + }(s) + } + // Run web service err := s.server.Run(s.config.Core.ListeningAddress) if err != nil { diff --git a/internal/server/handlers_auth.go b/internal/server/handlers_auth.go index 8684641..60ebd89 100644 --- a/internal/server/handlers_auth.go +++ b/internal/server/handlers_auth.go @@ -4,6 +4,8 @@ import ( "net/http" "strings" + log "github.com/sirupsen/logrus" + "github.com/gin-gonic/gin" ) @@ -95,6 +97,21 @@ func (s *Server) PostLogin(c *gin.Context) { } } + // Check if user already has a peer setup, if not create one + if s.config.Core.CreateInterfaceOnLogin && !adminAuthenticated { + users := s.users.GetUsersByMail(sessionData.Email) + + if len(users) == 0 { // Create vpn peer + err := s.CreateUser(User{ + Identifier: sessionData.Firstname + " " + sessionData.Lastname + " (Default)", + Email: sessionData.Email, + CreatedBy: sessionData.Email, + UpdatedBy: sessionData.Email, + }) + log.Errorf("Failed to automatically create vpn peer for %s: %v", sessionData.Email, err) + } + } + if err := s.updateSessionData(c, sessionData); err != nil { s.GetHandleError(c, http.StatusInternalServerError, "login error", "failed to save session") return diff --git a/internal/server/ldapsync.go b/internal/server/ldapsync.go new file mode 100644 index 0000000..646b7a3 --- /dev/null +++ b/internal/server/ldapsync.go @@ -0,0 +1,34 @@ +package server + +import ( + "time" + + "github.com/h44z/wg-portal/internal/ldap" + log "github.com/sirupsen/logrus" +) + +// SyncLdapAttributesWithWireGuard starts to synchronize the "disabled" attribute from ldap. +// Users will be automatically disabled once they are disabled in ldap. +// This method is blocking. +func (s *Server) SyncLdapAttributesWithWireGuard() error { + allUsers := s.users.GetAllUsers() + for i := range allUsers { + user := allUsers[i] + if user.LdapUser == nil { + continue // skip non ldap users + } + + if user.DeactivatedAt != nil { + continue // skip already disabled interfaces + } + + if ldap.IsLdapUserDisabled(allUsers[i].LdapUser.Attributes["userAccountControl"]) { + now := time.Now() + user.DeactivatedAt = &now + if err := s.UpdateUser(user, now); err != nil { + log.Errorf("Failed to disable user %s: %v", user.Email, err) + } + } + } + return nil +} diff --git a/internal/wireguard/template.go b/internal/wireguard/template.go index b1e5f59..25fcd4b 100644 --- a/internal/wireguard/template.go +++ b/internal/wireguard/template.go @@ -10,7 +10,8 @@ DNS = {{ .Server.DNSStr }} {{- end}} {{- if ne .Server.Mtu 0 -}} MTU = {{.Server.Mtu}} -{{- end -}} +{{- end}} + [Peer] PublicKey = {{ .Server.PublicKey }} {{- if .Client.PresharedKey -}}