Compare commits

...

7 Commits

Author SHA1 Message Date
Christoph Haas
83271b5d34 fix user edit bug, allow to delete users from the database (#40) 2022-03-15 23:34:55 +01:00
Alexis
cc50fcf8e6 Feat/ldap certificate connexion (#92)
* Give the way to connect against LDAP server with certificate and key

* fix(ldap) Update cert variable name

In order to be more explicit

Co-authored-by: Alexis Aurin <alexis@so6.pw>
2022-03-15 22:46:00 +01:00
Christoph Haas
5d4d06db81 fix invalid interface public key (#74) 2021-12-16 19:51:45 +01:00
ultram4rine
e581b3a69f Wireguard exporter friendly tags (#81)
* add friendly name

* add friendly name as option to configuration

* add friendly name configuration to readme
2021-12-16 19:35:15 +01:00
Alexander Beck
acb629f672 do not overwrite preshared key in CreatePeer (#77) 2021-12-10 16:52:44 +01:00
Christoph Haas
b5cb967e09 improve ldap logging (#67) 2021-11-07 13:20:16 +01:00
commonism
5a9918e00d docker-compose - use logging limits (#66)
- ldap sync is very noisy, limits/rotation required
 - can be verified with
   docker inspect -f '{{.HostConfig.LogConfig}}' 88…de
   {json-file map[max-file:3 max-size:10m]}

Co-authored-by: Markus Koetter <koetter@cispa.de>
2021-11-04 22:52:14 +01:00
17 changed files with 226 additions and 105 deletions

106
README.md
View File

@@ -108,57 +108,61 @@ For example: `CONFIG_FILE=/home/test/config.yml ./wg-portal-amd64`.
### Configuration Options
The following configuration options are available:
| environment | yaml | yaml_parent | default_value | description |
|-----------------------|-------------------|-------------|-------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------|
| LISTENING_ADDRESS | listeningAddress | core | :8123 | The address on which the web server is listening. Optional IP address and port, e.g.: 127.0.0.1:8080. |
| EXTERNAL_URL | externalUrl | core | http://localhost:8123 | The external URL where the web server is reachable. This link is used in emails that are created by the WireGuard Portal. |
| WEBSITE_TITLE | title | core | WireGuard VPN | The website title. |
| COMPANY_NAME | company | core | WireGuard Portal | The company name (for branding). |
| MAIL_FROM | mailFrom | core | WireGuard VPN <noreply@company.com> | The email address from which emails are sent. |
| LOGO_URL | logoUrl | core | /img/header-logo.png | The logo displayed in the page's header. |
| ADMIN_USER | adminUser | core | admin@wgportal.local | The administrator user. Must be a valid email address. |
| ADMIN_PASS | adminPass | core | wgportal | The administrator password. If unchanged, a random password will be set on first startup. |
| EDITABLE_KEYS | editableKeys | core | true | Allow to edit key-pairs in the UI. |
| CREATE_DEFAULT_PEER | createDefaultPeer | core | false | If an LDAP user logs in for the first time, a new WireGuard peer will be created on the WG_DEFAULT_DEVICE if this option is enabled. |
| SELF_PROVISIONING | selfProvisioning | core | false | Allow registered users to automatically create peers via the RESTful API. |
| LDAP_ENABLED | ldapEnabled | core | false | Enable or disable the LDAP backend. |
| SESSION_SECRET | sessionSecret | core | secret | Use a custom secret to encrypt session data. |
| DATABASE_TYPE | typ | database | sqlite | Either mysql or sqlite. |
| DATABASE_HOST | host | database | | The mysql server address. |
| DATABASE_PORT | port | database | | The mysql server port. |
| DATABASE_NAME | database | database | data/wg_portal.db | For sqlite database: the database file-path, otherwise the database name. |
| DATABASE_USERNAME | user | database | | The mysql user. |
| DATABASE_PASSWORD | password | database | | The mysql password. |
| EMAIL_HOST | host | email | 127.0.0.1 | The email server address. |
| EMAIL_PORT | port | email | 25 | The email server port. |
| EMAIL_TLS | tls | email | false | Use STARTTLS. DEPRECATED: use EMAIL_ENCRYPTION instead. |
| EMAIL_ENCRYPTION | encryption | email | none | Either none, tls or starttls. |
| EMAIL_CERT_VALIDATION | certcheck | email | false | Validate the email server certificate. |
| EMAIL_USERNAME | user | email | | An optional username for SMTP authentication. |
| EMAIL_PASSWORD | pass | email | | An optional password for SMTP authentication. |
| EMAIL_AUTHTYPE | auth | email | plain | Either plain, login or crammd5. If username and password are empty, this value is ignored. |
| WG_DEVICES | devices | wg | wg0 | A comma separated list of WireGuard devices. |
| WG_DEFAULT_DEVICE | defaultDevice | wg | wg0 | This device is used for auto-created peers (if CREATE_DEFAULT_PEER is enabled). |
| WG_CONFIG_PATH | configDirectory | wg | /etc/wireguard | If set, interface configuration updates will be written to this path, filename: <devicename>.conf. |
| MANAGE_IPS | manageIPAddresses | wg | true | Handle IP address setup of interface, only available on linux. |
| LDAP_URL | url | ldap | ldap://srv-ad01.company.local:389 | The LDAP server url. |
| LDAP_STARTTLS | startTLS | ldap | true | Use STARTTLS. |
| LDAP_CERT_VALIDATION | certcheck | ldap | false | Validate the LDAP server certificate. |
| LDAP_BASEDN | dn | ldap | DC=COMPANY,DC=LOCAL | The base DN for searching users. |
| LDAP_USER | user | ldap | company\\\\ldap_wireguard | The bind user. |
| LDAP_PASSWORD | pass | ldap | SuperSecret | The bind password. |
| LDAP_LOGIN_FILTER | loginFilter | ldap | (&(objectClass=organizationalPerson)(mail={{login_identifier}})(!userAccountControl:1.2.840.113556.1.4.803:=2)) | {{login_identifier}} will be replaced with the login email address. |
| LDAP_SYNC_FILTER | syncFilter | ldap | (&(objectClass=organizationalPerson)(!userAccountControl:1.2.840.113556.1.4.803:=2)(mail=*)) | The filter string for the LDAP synchronization service. |
| LDAP_ADMIN_GROUP | adminGroup | ldap | CN=WireGuardAdmins,OU=_O_IT,DC=COMPANY,DC=LOCAL | Users in this group are marked as administrators. |
| LDAP_ATTR_EMAIL | attrEmail | ldap | mail | User email attribute. |
| LDAP_ATTR_FIRSTNAME | attrFirstname | ldap | givenName | User firstname attribute. |
| LDAP_ATTR_LASTNAME | attrLastname | ldap | sn | User lastname attribute. |
| LDAP_ATTR_PHONE | attrPhone | ldap | telephoneNumber | User phone number attribute. |
| LDAP_ATTR_GROUPS | attrGroups | ldap | memberOf | User groups attribute. |
| LOG_LEVEL | | | debug | Specify log level, one of: trace, debug, info, off. |
| LOG_JSON | | | false | Format log output as JSON. |
| LOG_COLOR | | | true | Colorize log output. |
| CONFIG_FILE | | | config.yml | The config file path. |
| environment | yaml | yaml_parent | default_value | description |
|----------------------------|-------------------------|-------------|-------------------------------------------------|-------------------------------------------------------------------------------------------|
| LISTENING_ADDRESS | listeningAddress | core | :8123 | The address on which the web server is listening. Optional IP address and port, e.g.: 127.0.0.1:8080. |
| EXTERNAL_URL | externalUrl | core | http://localhost:8123 | The external URL where the web server is reachable. This link is used in emails that are created by the WireGuard Portal. |
| WEBSITE_TITLE | title | core | WireGuard VPN | The website title. |
| COMPANY_NAME | company | core | WireGuard Portal | The company name (for branding). |
| MAIL_FROM | mailFrom | core | WireGuard VPN <noreply@company.com> | The email address from which emails are sent. |
| LOGO_URL | logoUrl | core | /img/header-logo.png | The logo displayed in the page's header. |
| ADMIN_USER | adminUser | core | admin@wgportal.local | The administrator user. Must be a valid email address. |
| ADMIN_PASS | adminPass | core | wgportal | The administrator password. If unchanged, a random password will be set on first startup. |
| EDITABLE_KEYS | editableKeys | core | true | Allow to edit key-pairs in the UI. |
| CREATE_DEFAULT_PEER | createDefaultPeer | core | false | If an LDAP user logs in for the first time, a new WireGuard peer will be created on the WG_DEFAULT_DEVICE if this option is enabled. |
| SELF_PROVISIONING | selfProvisioning | core | false | Allow registered users to automatically create peers via the RESTful API. |
| WG_EXPORTER_FRIENDLY_NAMES | wgExporterFriendlyNames | core | false | Enable integration with [prometheus_wireguard_exporter friendly name](https://github.com/MindFlavor/prometheus_wireguard_exporter#friendly-tags). |
| LDAP_ENABLED | ldapEnabled | core | false | Enable or disable the LDAP backend. |
| SESSION_SECRET | sessionSecret | core | secret | Use a custom secret to encrypt session data. |
| DATABASE_TYPE | typ | database | sqlite | Either mysql or sqlite. |
| DATABASE_HOST | host | database | | The mysql server address. |
| DATABASE_PORT | port | database | | The mysql server port. |
| DATABASE_NAME | database | database | data/wg_portal.db | For sqlite database: the database file-path, otherwise the database name. |
| DATABASE_USERNAME | user | database | | The mysql user. |
| DATABASE_PASSWORD | password | database | | The mysql password. |
| EMAIL_HOST | host | email | 127.0.0.1 | The email server address. |
| EMAIL_PORT | port | email | 25 | The email server port. |
| EMAIL_TLS | tls | email | false | Use STARTTLS. DEPRECATED: use EMAIL_ENCRYPTION instead. |
| EMAIL_ENCRYPTION | encryption | email | none | Either none, tls or starttls. |
| EMAIL_CERT_VALIDATION | certcheck | email | false | Validate the email server certificate. |
| EMAIL_USERNAME | user | email | | An optional username for SMTP authentication. |
| EMAIL_PASSWORD | pass | email | | An optional password for SMTP authentication. |
| EMAIL_AUTHTYPE | auth | email | plain | Either plain, login or crammd5. If username and password are empty, this value is ignored. |
| WG_DEVICES | devices | wg | wg0 | A comma separated list of WireGuard devices. |
| WG_DEFAULT_DEVICE | defaultDevice | wg | wg0 | This device is used for auto-created peers (if CREATE_DEFAULT_PEER is enabled). |
| WG_CONFIG_PATH | configDirectory | wg | /etc/wireguard | If set, interface configuration updates will be written to this path, filename: <devicename>.conf. |
| MANAGE_IPS | manageIPAddresses | wg | true | Handle IP address setup of interface, only available on linux. |
| LDAP_URL | url | ldap | ldap://srv-ad01.company.local:389 | The LDAP server url. |
| LDAP_STARTTLS | startTLS | ldap | true | Use STARTTLS. |
| LDAP_CERT_VALIDATION | certcheck | ldap | false | Validate the LDAP server certificate. |
| LDAP_BASEDN | dn | ldap | DC=COMPANY,DC=LOCAL | The base DN for searching users. |
| LDAP_USER | user | ldap | company\\\\ldap_wireguard | The bind user. |
| LDAP_PASSWORD | pass | ldap | SuperSecret | The bind password. |
| LDAP_LOGIN_FILTER | loginFilter | ldap | (&(objectClass=organizationalPerson)(mail={{login_identifier}})(!userAccountControl:1.2.840.113556.1.4.803:=2)) | {{login_identifier}} will be replaced with the login email address. |
| LDAP_SYNC_FILTER | syncFilter | ldap | (&(objectClass=organizationalPerson)(!userAccountControl:1.2.840.113556.1.4.803:=2)(mail=*)) | The filter string for the LDAP synchronization service. |
| LDAP_ADMIN_GROUP | adminGroup | ldap | CN=WireGuardAdmins,OU=_O_IT,DC=COMPANY,DC=LOCAL | Users in this group are marked as administrators. |
| LDAP_ATTR_EMAIL | attrEmail | ldap | mail | User email attribute. |
| LDAP_ATTR_FIRSTNAME | attrFirstname | ldap | givenName | User firstname attribute. |
| LDAP_ATTR_LASTNAME | attrLastname | ldap | sn | User lastname attribute. |
| LDAP_ATTR_PHONE | attrPhone | ldap | telephoneNumber | User phone number attribute. |
| LDAP_ATTR_GROUPS | attrGroups | ldap | memberOf | User groups attribute. |
| LDAP_CERT_CONN | ldapCertConn | ldap | false | Allow connection with certificate against LDAP server without user/password |
| LDAPTLS_CERT | ldapTlsCert | ldap | | The LDAP cert's path |
| LDAPTLS_KEY | ldapTlsKey | ldap | | The LDAP key's path |
| LOG_LEVEL | | | debug | Specify log level, one of: trace, debug, info, off. |
| LOG_JSON | | | false | Format log output as JSON. |
| LOG_COLOR | | | true | Colorize log output. |
| CONFIG_FILE | | | config.yml | The config file path. |
### Sample yaml configuration
config.yml:

View File

@@ -76,6 +76,11 @@
<button type="submit" class="btn btn-primary">Save</button>
<a href="/admin/users/" class="btn btn-secondary">Cancel</a>
{{if eq $.Session.IsAdmin true}}
{{if eq .User.Source "db"}}
<a href="/admin/users/delete?pkey={{.User.Email}}" data-toggle="confirmation" data-title="Really delete user and associated peers?" title="Delete user and associated peers" class="btn btn-danger float-right">Delete</a>
{{end}}
{{end}}
</form>
</div>
{{template "prt_footer.html" .}}

View File

@@ -4,6 +4,10 @@ services:
image: h44z/wg-portal:1.0.6
container_name: wg-portal
restart: unless-stopped
logging:
options:
max-size: "10m"
max-file: "3"
cap_add:
- NET_ADMIN
network_mode: "host"

View File

@@ -2,6 +2,7 @@ package ldap
import (
"crypto/tls"
"io/ioutil"
"strings"
"github.com/gin-gonic/gin"
@@ -154,23 +155,49 @@ func (provider Provider) GetUserModel(ctx *authentication.AuthContext) (*authent
}
func (provider Provider) open() (*ldap.Conn, error) {
tlsConfig := &tls.Config{InsecureSkipVerify: !provider.config.CertValidation}
var tlsConfig *tls.Config
if provider.config.LdapCertConn {
cert_plain, err := ioutil.ReadFile(provider.config.LdapTlsCert)
if err != nil {
return nil, errors.WithMessage(err, "failed to load the certificate")
}
key, err := ioutil.ReadFile(provider.config.LdapTlsKey)
if err != nil {
return nil, errors.WithMessage(err, "failed to load the key")
}
cert_x509, err := tls.X509KeyPair(cert_plain, key)
if err != nil {
return nil, errors.WithMessage(err, "failed X509")
}
tlsConfig = &tls.Config{Certificates: []tls.Certificate{cert_x509}}
} else {
tlsConfig = &tls.Config{InsecureSkipVerify: !provider.config.CertValidation}
}
conn, err := ldap.DialURL(provider.config.URL, ldap.DialWithTLSConfig(tlsConfig))
if err != nil {
return nil, err
return nil, errors.WithMessage(err, "failed to connect to LDAP")
}
if provider.config.StartTLS {
// Reconnect with TLS
err = conn.StartTLS(tlsConfig)
if err != nil {
return nil, err
return nil, errors.WithMessage(err, "failed to start TLS session")
}
}
err = conn.Bind(provider.config.BindUser, provider.config.BindPass)
if err != nil {
return nil, err
return nil, errors.WithMessage(err, "failed to bind user")
}
return conn, nil

View File

@@ -4,7 +4,6 @@ import (
gldap "github.com/go-ldap/ldap/v3"
)
type Type string
const (
@@ -26,8 +25,11 @@ type Config struct {
PhoneAttribute string `yaml:"attrPhone" envconfig:"LDAP_ATTR_PHONE"`
GroupMemberAttribute string `yaml:"attrGroups" envconfig:"LDAP_ATTR_GROUPS"`
LoginFilter string `yaml:"loginFilter" envconfig:"LDAP_LOGIN_FILTER"` // {{login_identifier}} gets replaced with the login email address
SyncFilter string `yaml:"syncFilter" envconfig:"LDAP_SYNC_FILTER"`
AdminLdapGroup string `yaml:"adminGroup" envconfig:"LDAP_ADMIN_GROUP"` // Members of this group receive admin rights in WG-Portal
LoginFilter string `yaml:"loginFilter" envconfig:"LDAP_LOGIN_FILTER"` // {{login_identifier}} gets replaced with the login email address
SyncFilter string `yaml:"syncFilter" envconfig:"LDAP_SYNC_FILTER"`
AdminLdapGroup string `yaml:"adminGroup" envconfig:"LDAP_ADMIN_GROUP"` // Members of this group receive admin rights in WG-Portal
AdminLdapGroup_ *gldap.DN `yaml:"-"`
LdapCertConn bool `yaml:"ldapCertConn" envconfig:"LDAP_CERT_CONN"`
LdapTlsCert string `yaml:"ldapTlsCert" envconfig:"LDAPTLS_CERT"`
LdapTlsKey string `yaml:"ldapTlsKey" envconfig:"LDAPTLS_KEY"`
}

View File

@@ -2,6 +2,7 @@ package ldap
import (
"crypto/tls"
"io/ioutil"
"github.com/go-ldap/ldap/v3"
"github.com/pkg/errors"
@@ -14,7 +15,33 @@ type RawLdapData struct {
}
func Open(cfg *Config) (*ldap.Conn, error) {
tlsConfig := &tls.Config{InsecureSkipVerify: !cfg.CertValidation}
var tlsConfig *tls.Config
if cfg.LdapCertConn {
cert_plain, err := ioutil.ReadFile(cfg.LdapTlsCert)
if err != nil {
return nil, errors.WithMessage(err, "failed to load the certificate")
}
key, err := ioutil.ReadFile(cfg.LdapTlsKey)
if err != nil {
return nil, errors.WithMessage(err, "failed to load the key")
}
cert_x509, err := tls.X509KeyPair(cert_plain, key)
if err != nil {
return nil, errors.WithMessage(err, "failed X509")
}
tlsConfig = &tls.Config{Certificates: []tls.Certificate{cert_x509}}
} else {
tlsConfig = &tls.Config{InsecureSkipVerify: !cfg.CertValidation}
}
conn, err := ldap.DialURL(cfg.URL, ldap.DialWithTLSConfig(tlsConfig))
if err != nil {
return nil, errors.Wrap(err, "failed to connect to LDAP")

View File

@@ -67,6 +67,7 @@ type Config struct {
EditableKeys bool `yaml:"editableKeys" envconfig:"EDITABLE_KEYS"`
CreateDefaultPeer bool `yaml:"createDefaultPeer" envconfig:"CREATE_DEFAULT_PEER"`
SelfProvisioningAllowed bool `yaml:"selfProvisioning" envconfig:"SELF_PROVISIONING"`
WGExoprterFriendlyNames bool `yaml:"wgExporterFriendlyNames" envconfig:"WG_EXPORTER_FRIENDLY_NAMES"`
LdapEnabled bool `yaml:"ldapEnabled" envconfig:"LDAP_ENABLED"`
SessionSecret string `yaml:"sessionSecret" envconfig:"SESSION_SECRET"`
LogoUrl string `yaml:"logoUrl" envconfig:"LOGO_URL"`
@@ -91,6 +92,7 @@ func NewConfig() *Config {
cfg.Core.AdminPassword = "wgportal"
cfg.Core.LdapEnabled = false
cfg.Core.EditableKeys = true
cfg.Core.WGExoprterFriendlyNames = false
cfg.Core.SessionSecret = "secret"
cfg.Database.Typ = "sqlite"

View File

@@ -112,7 +112,7 @@ func (s *Server) GetInterfaceConfig(c *gin.Context) {
currentSession := GetSessionData(c)
device := s.peers.GetDevice(currentSession.DeviceName)
peers := s.peers.GetActivePeers(device.DeviceName)
cfg, err := device.GetConfigFile(peers)
cfg, err := device.GetConfigFile(peers, s.config.Core.WGExoprterFriendlyNames)
if err != nil {
s.GetHandleError(c, http.StatusInternalServerError, "ConfigFile error", err.Error())
return

View File

@@ -236,7 +236,7 @@ func (s *Server) GetPeerConfig(c *gin.Context) {
return
}
cfg, err := peer.GetConfigFile(s.peers.GetDevice(currentSession.DeviceName))
cfg, err := peer.GetConfigFile(s.peers.GetDevice(peer.DeviceName))
if err != nil {
s.GetHandleError(c, http.StatusInternalServerError, "ConfigFile error", err.Error())
return

View File

@@ -83,6 +83,26 @@ func (s *Server) GetAdminUsersEdit(c *gin.Context) {
})
}
func (s *Server) GetAdminUsersDelete(c *gin.Context) {
user := s.users.GetUserUnscoped(c.Query("pkey"))
if user == nil {
SetFlashMessage(c, "invalid user", "danger")
c.Redirect(http.StatusSeeOther, "/admin/users/")
return
}
urlEncodedKey := url.QueryEscape(c.Query("pkey"))
if err := s.HardDeleteUser(*user); err != nil {
SetFlashMessage(c, "failed to delete user: "+err.Error(), "danger")
c.Redirect(http.StatusSeeOther, "/admin/users/edit?pkey="+urlEncodedKey+"&formerr=delete")
return
}
SetFlashMessage(c, "user deleted successfully", "success")
c.Redirect(http.StatusSeeOther, "/admin/users/")
}
func (s *Server) PostAdminUsersEdit(c *gin.Context) {
currentUser := s.users.GetUserUnscoped(c.Query("pkey"))
if currentUser == nil {
@@ -113,7 +133,7 @@ func (s *Server) PostAdminUsersEdit(c *gin.Context) {
} else {
formUser.DeletedAt = gorm.DeletedAt{}
}
formUser.IsAdmin = c.PostForm("isadmin") == "true"
formUser.IsAdmin = c.PostForm("isadmin") != ""
if err := s.UpdateUser(formUser); err != nil {
_ = s.updateFormInSession(c, formUser)

View File

@@ -44,14 +44,14 @@ func (s *Server) SyncLdapWithUserDatabase() {
logrus.Info("ldap user synchronization stopped")
}
func (s Server)userIsInAdminGroup(ldapData *ldap.RawLdapData) bool {
func (s Server) userIsInAdminGroup(ldapData *ldap.RawLdapData) bool {
if s.config.LDAP.AdminLdapGroup_ == nil {
return false
return false
}
for _, group := range ldapData.RawAttributes[s.config.LDAP.GroupMemberAttribute] {
var dn,_ = gldap.ParseDN(string(group))
var dn, _ = gldap.ParseDN(string(group))
if s.config.LDAP.AdminLdapGroup_.Equal(dn) {
return true
return true
}
}
return false
@@ -114,7 +114,7 @@ func (s *Server) disableMissingLdapUsers(ldapUsers []ldap.RawLdapData) {
}
}
if err := s.users.DeleteUser(&activeUsers[i]); err != nil {
if err := s.users.DeleteUser(&activeUsers[i], true); err != nil {
logrus.Errorf("failed to delete deactivated user %s in database: %v", activeUsers[i].Email, err)
}
}

View File

@@ -64,6 +64,7 @@ func SetupRoutes(s *Server) {
admin.GET("/users/create", s.GetAdminUsersCreate)
admin.POST("/users/create", s.PostAdminUsersCreate)
admin.GET("/users/edit", s.GetAdminUsersEdit)
admin.GET("/users/delete", s.GetAdminUsersDelete)
admin.POST("/users/edit", s.PostAdminUsersEdit)
// User routes

View File

@@ -103,16 +103,21 @@ func (s *Server) CreatePeer(device string, peer wireguard.Peer) error {
}
peer.SetIPAddresses(peerIPs...)
}
if peer.PrivateKey == "" && dev.Type == wireguard.DeviceTypeServer { // if private key is empty create a new one
if peer.PresharedKey == "" && dev.Type == wireguard.DeviceTypeServer { // if preshared key is empty create a new one
psk, err := wgtypes.GenerateKey()
if err != nil {
return errors.Wrap(err, "failed to generate key")
}
peer.PresharedKey = psk.String()
}
if peer.PrivateKey == "" && peer.PublicKey == "" && dev.Type == wireguard.DeviceTypeServer { // if private key is empty create a new one
key, err := wgtypes.GeneratePrivateKey()
if err != nil {
return errors.Wrap(err, "failed to generate private key")
}
peer.PresharedKey = psk.String()
peer.PrivateKey = key.String()
peer.PublicKey = key.PublicKey().String()
}
@@ -204,7 +209,7 @@ func (s *Server) WriteWireGuardConfigFile(device string) error {
}
dev := s.peers.GetDevice(device)
cfg, err := dev.GetConfigFile(s.peers.GetActivePeers(device))
cfg, err := dev.GetConfigFile(s.peers.GetActivePeers(device), s.config.Core.WGExoprterFriendlyNames)
if err != nil {
return errors.WithMessage(err, "failed to get config file")
}
@@ -248,10 +253,6 @@ func (s *Server) CreateUser(user users.User, device string) error {
// UpdateUser updates the user in the database. If the user is marked as deleted, it will get remove from the database.
// Also, if the user is re-enabled, all it's linked WireGuard peers will be activated again.
func (s *Server) UpdateUser(user users.User) error {
if user.DeletedAt.Valid {
return s.DeleteUser(user)
}
currentUser := s.users.GetUserUnscoped(user.Email)
// Hash user password (if set)
@@ -270,7 +271,12 @@ func (s *Server) UpdateUser(user users.User) error {
return errors.WithMessage(err, "failed to update user in manager")
}
// If user was deleted (disabled), reactivate it's peers
// Set to deleted (disabled) if user's deletedAt date is not empty
if user.DeletedAt.Valid {
return s.DeleteUser(user)
}
// Otherwise, if user was deleted (disabled), reactivate it's peers
if currentUser.DeletedAt.Valid {
for _, peer := range s.peers.GetPeersByMail(user.Email) {
now := time.Now()
@@ -284,24 +290,38 @@ func (s *Server) UpdateUser(user users.User) error {
return nil
}
// DeleteUser removes the user from the database.
// DeleteUser soft-deletes the user from the database (disable the user).
// Also, if the user has linked WireGuard peers, they will be deactivated.
func (s *Server) DeleteUser(user users.User) error {
currentUser := s.users.GetUserUnscoped(user.Email)
// Update in database
if err := s.users.DeleteUser(&user); err != nil {
if err := s.users.DeleteUser(&user, true); err != nil {
return errors.WithMessage(err, "failed to disable user in manager")
}
// Disable users peers
for _, peer := range s.peers.GetPeersByMail(user.Email) {
now := time.Now()
peer.DeactivatedAt = &now
if err := s.UpdatePeer(peer, now); err != nil {
logrus.Errorf("failed to update deactivated peer %s for %s: %v", peer.PublicKey, user.Email, err)
}
}
return nil
}
// HardDeleteUser removes the user from the database.
// Also, if the user has linked WireGuard peers, they will be deleted.
func (s *Server) HardDeleteUser(user users.User) error {
// Update in database
if err := s.users.DeleteUser(&user, false); err != nil {
return errors.WithMessage(err, "failed to delete user in manager")
}
// If user was active, disable it's peers
if !currentUser.DeletedAt.Valid {
for _, peer := range s.peers.GetPeersByMail(user.Email) {
now := time.Now()
peer.DeactivatedAt = &now
if err := s.UpdatePeer(peer, now); err != nil {
logrus.Errorf("failed to update deactivated peer %s for %s: %v", peer.PublicKey, user.Email, err)
}
// remove all linked peers
for _, peer := range s.peers.GetPeersByMail(user.Email) {
if err := s.DeletePeer(peer); err != nil {
logrus.Errorf("failed to delete peer %s for %s: %v", peer.PublicKey, user.Email, err)
}
}

View File

@@ -161,9 +161,14 @@ func (m Manager) UpdateUser(user *User) error {
return nil
}
func (m Manager) DeleteUser(user *User) error {
func (m Manager) DeleteUser(user *User, soft bool) error {
user.Email = strings.ToLower(user.Email)
res := m.db.Delete(user)
var res *gorm.DB
if soft {
res = m.db.Delete(user)
} else {
res = m.db.Unscoped().Delete(user)
}
if res.Error != nil {
return errors.Wrapf(res.Error, "failed to update user %s", user.Email)
}

View File

@@ -29,7 +29,7 @@ type User struct {
// required fields
Email string `gorm:"primaryKey" form:"email" binding:"required,email"`
Source UserSource
IsAdmin bool
IsAdmin bool `form:"isadmin"`
// optional fields
Firstname string `form:"firstname" binding:"required"`

View File

@@ -338,12 +338,13 @@ func (d Device) GetConfig() wgtypes.Config {
return cfg
}
func (d Device) GetConfigFile(peers []Peer) ([]byte, error) {
func (d Device) GetConfigFile(peers []Peer, friendlyNames bool) ([]byte, error) {
var tplBuff bytes.Buffer
err := templateCache.ExecuteTemplate(&tplBuff, "interface.tpl", gin.H{
"Peers": peers,
"Interface": d,
"Peers": peers,
"Interface": d,
"FriendlyNames": friendlyNames,
})
if err != nil {
return nil, errors.Wrap(err, "failed to execute server template")

View File

@@ -56,6 +56,9 @@ PostDown = {{ .Interface.PostDown }}
# -WGP- PrivateKey: {{.PrivateKey}}
{{- end}}
[Peer]
{{- if $.FriendlyNames}}
# friendly_name = {{ .Identifier }}
{{- end}}
PublicKey = {{ .PublicKey }}
{{- if .PresharedKey}}
PresharedKey = {{ .PresharedKey }}