mirror of
https://github.com/h44z/wg-portal.git
synced 2025-04-19 08:55:12 +00:00
commit
979cec7d83
2
Makefile
2
Makefile
@ -52,7 +52,7 @@ docker-push:
|
|||||||
docker push $(IMAGE)
|
docker push $(IMAGE)
|
||||||
|
|
||||||
api-docs:
|
api-docs:
|
||||||
cd internal/server; swag init --propertyStrategy pascalcase --parseDependency --parseInternal --generalInfo api.go
|
cd internal; swag init --propertyStrategy pascalcase --parseInternal --generalInfo server/api.go --output server/docs/
|
||||||
$(GOCMD) fmt internal/server/docs/docs.go
|
$(GOCMD) fmt internal/server/docs/docs.go
|
||||||
|
|
||||||
$(BUILDDIR)/%-amd64: cmd/%/main.go dep phony
|
$(BUILDDIR)/%-amd64: cmd/%/main.go dep phony
|
||||||
|
113
README.md
113
README.md
@ -31,6 +31,7 @@ It also supports LDAP (Active Directory or OpenLDAP) as authentication provider.
|
|||||||
* Can be used with existing WireGuard setups
|
* Can be used with existing WireGuard setups
|
||||||
* Support for multiple WireGuard interfaces
|
* Support for multiple WireGuard interfaces
|
||||||
* REST API for management and client deployment
|
* REST API for management and client deployment
|
||||||
|
* Peer Expiry Feature
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@ -108,61 +109,63 @@ For example: `CONFIG_FILE=/home/test/config.yml ./wg-portal-amd64`.
|
|||||||
### Configuration Options
|
### Configuration Options
|
||||||
The following configuration options are available:
|
The following configuration options are available:
|
||||||
|
|
||||||
| environment | yaml | yaml_parent | default_value | description |
|
| 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. |
|
| 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. |
|
| 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. |
|
| WEBSITE_TITLE | title | core | WireGuard VPN | The website title. |
|
||||||
| COMPANY_NAME | company | core | WireGuard Portal | The company name (for branding). |
|
| 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. |
|
| 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. |
|
| 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_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. |
|
| 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. |
|
| 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. |
|
| 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. |
|
| 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). |
|
| 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. |
|
| LDAP_ENABLED | ldapEnabled | core | false | Enable or disable the LDAP backend. |
|
||||||
| SESSION_SECRET | sessionSecret | core | secret | Use a custom secret to encrypt session data. |
|
| SESSION_SECRET | sessionSecret | core | secret | Use a custom secret to encrypt session data. |
|
||||||
| DATABASE_TYPE | typ | database | sqlite | Either mysql or sqlite. |
|
| BACKGROUND_TASK_INTERVAL | backgroundTaskInterval | core | 900 | The interval (in seconds) for the background tasks (like peer expiry check). |
|
||||||
| DATABASE_HOST | host | database | | The mysql server address. |
|
| DATABASE_TYPE | typ | database | sqlite | Either mysql or sqlite. |
|
||||||
| DATABASE_PORT | port | database | | The mysql server port. |
|
| DATABASE_HOST | host | database | | The mysql server address. |
|
||||||
| DATABASE_NAME | database | database | data/wg_portal.db | For sqlite database: the database file-path, otherwise the database name. |
|
| DATABASE_PORT | port | database | | The mysql server port. |
|
||||||
| DATABASE_USERNAME | user | database | | The mysql user. |
|
| DATABASE_NAME | database | database | data/wg_portal.db | For sqlite database: the database file-path, otherwise the database name. |
|
||||||
| DATABASE_PASSWORD | password | database | | The mysql password. |
|
| DATABASE_USERNAME | user | database | | The mysql user. |
|
||||||
| EMAIL_HOST | host | email | 127.0.0.1 | The email server address. |
|
| DATABASE_PASSWORD | password | database | | The mysql password. |
|
||||||
| EMAIL_PORT | port | email | 25 | The email server port. |
|
| EMAIL_HOST | host | email | 127.0.0.1 | The email server address. |
|
||||||
| EMAIL_TLS | tls | email | false | Use STARTTLS. DEPRECATED: use EMAIL_ENCRYPTION instead. |
|
| EMAIL_PORT | port | email | 25 | The email server port. |
|
||||||
| EMAIL_ENCRYPTION | encryption | email | none | Either none, tls or starttls. |
|
| EMAIL_TLS | tls | email | false | Use STARTTLS. DEPRECATED: use EMAIL_ENCRYPTION instead. |
|
||||||
| EMAIL_CERT_VALIDATION | certcheck | email | false | Validate the email server certificate. |
|
| EMAIL_ENCRYPTION | encryption | email | none | Either none, tls or starttls. |
|
||||||
| EMAIL_USERNAME | user | email | | An optional username for SMTP authentication. |
|
| EMAIL_CERT_VALIDATION | certcheck | email | false | Validate the email server certificate. |
|
||||||
| EMAIL_PASSWORD | pass | email | | An optional password for SMTP authentication. |
|
| EMAIL_USERNAME | user | email | | An optional username for SMTP authentication. |
|
||||||
| EMAIL_AUTHTYPE | auth | email | plain | Either plain, login or crammd5. If username and password are empty, this value is ignored. |
|
| EMAIL_PASSWORD | pass | email | | An optional password for SMTP authentication. |
|
||||||
| WG_DEVICES | devices | wg | wg0 | A comma separated list of WireGuard devices. |
|
| EMAIL_AUTHTYPE | auth | email | plain | Either plain, login or crammd5. If username and password are empty, this value is ignored. |
|
||||||
| WG_DEFAULT_DEVICE | defaultDevice | wg | wg0 | This device is used for auto-created peers (if CREATE_DEFAULT_PEER is enabled). |
|
| WG_DEVICES | devices | wg | wg0 | A comma separated list of WireGuard devices. |
|
||||||
| WG_CONFIG_PATH | configDirectory | wg | /etc/wireguard | If set, interface configuration updates will be written to this path, filename: <devicename>.conf. |
|
| WG_DEFAULT_DEVICE | defaultDevice | wg | wg0 | This device is used for auto-created peers (if CREATE_DEFAULT_PEER is enabled). |
|
||||||
| MANAGE_IPS | manageIPAddresses | wg | true | Handle IP address setup of interface, only available on linux. |
|
| WG_CONFIG_PATH | configDirectory | wg | /etc/wireguard | If set, interface configuration updates will be written to this path, filename: <devicename>.conf. |
|
||||||
| LDAP_URL | url | ldap | ldap://srv-ad01.company.local:389 | The LDAP server url. |
|
| MANAGE_IPS | manageIPAddresses | wg | true | Handle IP address setup of interface, only available on linux. |
|
||||||
| LDAP_STARTTLS | startTLS | ldap | true | Use STARTTLS. |
|
| USER_MANAGE_PEERS | userManagePeers | wg | false | Logged in user can create or update peers (partially). |
|
||||||
| LDAP_CERT_VALIDATION | certcheck | ldap | false | Validate the LDAP server certificate. |
|
| LDAP_URL | url | ldap | ldap://srv-ad01.company.local:389 | The LDAP server url. |
|
||||||
| LDAP_BASEDN | dn | ldap | DC=COMPANY,DC=LOCAL | The base DN for searching users. |
|
| LDAP_STARTTLS | startTLS | ldap | true | Use STARTTLS. |
|
||||||
| LDAP_USER | user | ldap | company\\\\ldap_wireguard | The bind user. |
|
| LDAP_CERT_VALIDATION | certcheck | ldap | false | Validate the LDAP server certificate. |
|
||||||
| LDAP_PASSWORD | pass | ldap | SuperSecret | The bind password. |
|
| LDAP_BASEDN | dn | ldap | DC=COMPANY,DC=LOCAL | The base DN for searching users. |
|
||||||
| 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_USER | user | ldap | company\\\\ldap_wireguard | The bind user. |
|
||||||
| 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_PASSWORD | pass | ldap | SuperSecret | The bind password. |
|
||||||
| LDAP_ADMIN_GROUP | adminGroup | ldap | CN=WireGuardAdmins,OU=_O_IT,DC=COMPANY,DC=LOCAL | Users in this group are marked as administrators. |
|
| 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_ATTR_EMAIL | attrEmail | ldap | mail | User email attribute. |
|
| 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_ATTR_FIRSTNAME | attrFirstname | ldap | givenName | User firstname attribute. |
|
| LDAP_ADMIN_GROUP | adminGroup | ldap | CN=WireGuardAdmins,OU=_O_IT,DC=COMPANY,DC=LOCAL | Users in this group are marked as administrators. |
|
||||||
| LDAP_ATTR_LASTNAME | attrLastname | ldap | sn | User lastname attribute. |
|
| LDAP_ATTR_EMAIL | attrEmail | ldap | mail | User email attribute. |
|
||||||
| LDAP_ATTR_PHONE | attrPhone | ldap | telephoneNumber | User phone number attribute. |
|
| LDAP_ATTR_FIRSTNAME | attrFirstname | ldap | givenName | User firstname attribute. |
|
||||||
| LDAP_ATTR_GROUPS | attrGroups | ldap | memberOf | User groups attribute. |
|
| LDAP_ATTR_LASTNAME | attrLastname | ldap | sn | User lastname attribute. |
|
||||||
| LDAP_CERT_CONN | ldapCertConn | ldap | false | Allow connection with certificate against LDAP server without user/password |
|
| LDAP_ATTR_PHONE | attrPhone | ldap | telephoneNumber | User phone number attribute. |
|
||||||
| LDAPTLS_CERT | ldapTlsCert | ldap | | The LDAP cert's path |
|
| LDAP_ATTR_GROUPS | attrGroups | ldap | memberOf | User groups attribute. |
|
||||||
| LDAPTLS_KEY | ldapTlsKey | ldap | | The LDAP key's path |
|
| LDAP_CERT_CONN | ldapCertConn | ldap | false | Allow connection with certificate against LDAP server without user/password |
|
||||||
| LOG_LEVEL | | | debug | Specify log level, one of: trace, debug, info, off. |
|
| LDAPTLS_CERT | ldapTlsCert | ldap | | The LDAP cert's path |
|
||||||
| LOG_JSON | | | false | Format log output as JSON. |
|
| LDAPTLS_KEY | ldapTlsKey | ldap | | The LDAP key's path |
|
||||||
| LOG_COLOR | | | true | Colorize log output. |
|
| LOG_LEVEL | | | debug | Specify log level, one of: trace, debug, info, off. |
|
||||||
| CONFIG_FILE | | | config.yml | The config file path. |
|
| 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
|
### Sample yaml configuration
|
||||||
config.yml:
|
config.yml:
|
||||||
|
@ -73,6 +73,10 @@ pre{background:#f7f7f9}iframe{overflow:hidden;border:none}@media (min-width: 768
|
|||||||
color: #d03131;
|
color: #d03131;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.expiring-peer {
|
||||||
|
color: #d09d12;
|
||||||
|
}
|
||||||
|
|
||||||
.tokenfield .token {
|
.tokenfield .token {
|
||||||
border-radius: 0px;
|
border-radius: 0px;
|
||||||
border: 1px solid #1a1a1a;
|
border: 1px solid #1a1a1a;
|
||||||
@ -105,4 +109,10 @@ a.advanced-settings.collapsed:before {
|
|||||||
|
|
||||||
.text-blue {
|
.text-blue {
|
||||||
color: #0057bb;
|
color: #0057bb;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 992px) {
|
||||||
|
.pull-right-lg {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
}
|
}
|
@ -106,7 +106,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<div class="form-group col-md-12">
|
<div class="form-group col-md-6">
|
||||||
<div class="custom-control custom-switch">
|
<div class="custom-control custom-switch">
|
||||||
<input class="custom-control-input" name="isdisabled" type="checkbox" value="true" id="server_Disabled" {{if .Peer.DeactivatedAt}}checked{{end}}>
|
<input class="custom-control-input" name="isdisabled" type="checkbox" value="true" id="server_Disabled" {{if .Peer.DeactivatedAt}}checked{{end}}>
|
||||||
<label class="custom-control-label" for="server_Disabled">
|
<label class="custom-control-label" for="server_Disabled">
|
||||||
@ -120,6 +120,10 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group col-md-6">
|
||||||
|
<label for="expires_at">Expires At</label>
|
||||||
|
<input type="date" name="expires_at" pattern="\d{4}-\d{2}-\d{2}" class="form-control" id="expires_at" placeholder="" value="{{formatDate .Peer.ExpiresAt}}" min="2022-01-01">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
@ -185,7 +189,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<div class="form-group col-md-12">
|
<div class="form-group col-md-6">
|
||||||
<div class="custom-control custom-switch">
|
<div class="custom-control custom-switch">
|
||||||
<input class="custom-control-input" name="isdisabled" type="checkbox" value="true" id="client_Disabled" {{if .Peer.DeactivatedAt}}checked{{end}}>
|
<input class="custom-control-input" name="isdisabled" type="checkbox" value="true" id="client_Disabled" {{if .Peer.DeactivatedAt}}checked{{end}}>
|
||||||
<label class="custom-control-label" for="client_Disabled">
|
<label class="custom-control-label" for="client_Disabled">
|
||||||
@ -193,6 +197,10 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group col-md-6">
|
||||||
|
<label for="expires_at">Expires At</label>
|
||||||
|
<input type="date" name="expires_at" pattern="\d{4}-\d{2}-\d{2}" class="form-control" id="expires_at" placeholder="" value="{{formatDate .Peer.ExpiresAt}}" min="2022-01-01">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
@ -170,7 +170,7 @@
|
|||||||
<!-- online check -->
|
<!-- online check -->
|
||||||
<span title="Online status" class="online-status" id="online-{{$p.UID}}" data-pkey="{{$p.PublicKey}}"><i class="fas fa-unlink"></i></span>
|
<span title="Online status" class="online-status" id="online-{{$p.UID}}" data-pkey="{{$p.PublicKey}}"><i class="fas fa-unlink"></i></span>
|
||||||
</th>
|
</th>
|
||||||
<td>{{$p.Identifier}}</td>
|
<td>{{$p.Identifier}}{{if $p.WillExpire}} <i class="fas fa-hourglass-end expiring-peer" data-toggle="tooltip" data-placement="right" title="" data-original-title="Expires at: {{formatDate $p.ExpiresAt}}"></i>{{end}}</td>
|
||||||
<td>{{$p.PublicKey}}</td>
|
<td>{{$p.PublicKey}}</td>
|
||||||
{{if eq $.Device.Type "server"}}
|
{{if eq $.Device.Type "server"}}
|
||||||
<td>{{$p.Email}}</td>
|
<td>{{$p.Email}}</td>
|
||||||
@ -239,12 +239,18 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-md-3">
|
<div class="col-md-3">
|
||||||
{{if eq $.Device.Type "server"}}
|
{{if eq $.Device.Type "server"}}
|
||||||
<img class="list-image-large" src="/user/qrcode?pkey={{$p.PublicKey}}"/>
|
<img class="list-image-large" loading="lazy" alt="Configuration QR Code" src="/user/qrcode?pkey={{$p.PublicKey}}"/>
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3">
|
<div class="col-md-3">
|
||||||
|
{{if $p.DeactivatedAt}}
|
||||||
|
<div class="pull-right-lg mt-lg-5 disabled-peer">Peer is disabled! <i class="fas fa-comment-dots" data-toggle="tooltip" data-placement="left" title="" data-original-title="Reason: {{$p.DeactivatedReason}}"></i></div>
|
||||||
|
{{end}}
|
||||||
|
{{if $p.WillExpire}}
|
||||||
|
<div class="pull-right-lg mt-lg-5 expiring-peer"><i class="fas fa-exclamation-triangle"></i> Peer will expire on {{ formatDate $p.ExpiresAt}}</div>
|
||||||
|
{{end}}
|
||||||
{{if eq $.Device.Type "server"}}
|
{{if eq $.Device.Type "server"}}
|
||||||
<div class="float-right mt-5">
|
<div class="pull-right-lg mt-lg-5 mt-md-3">
|
||||||
<a href="/admin/peer/download?pkey={{$p.PublicKey}}" class="btn btn-primary" title="Download configuration">Download</a>
|
<a href="/admin/peer/download?pkey={{$p.PublicKey}}" class="btn btn-primary" title="Download configuration">Download</a>
|
||||||
<a href="/admin/peer/email?pkey={{$p.PublicKey}}" class="btn btn-primary" title="Send configuration via Email">Email</a>
|
<a href="/admin/peer/email?pkey={{$p.PublicKey}}" class="btn btn-primary" title="Send configuration via Email">Email</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -49,7 +49,7 @@
|
|||||||
<!-- online check -->
|
<!-- online check -->
|
||||||
<span class="online-status" id="online-{{$p.UID}}" data-pkey="{{$p.PublicKey}}"><i class="fas fa-unlink"></i></span>
|
<span class="online-status" id="online-{{$p.UID}}" data-pkey="{{$p.PublicKey}}"><i class="fas fa-unlink"></i></span>
|
||||||
</th>
|
</th>
|
||||||
<td>{{$p.Identifier}}</td>
|
<td>{{$p.Identifier}}{{if $p.WillExpire}} <i class="fas fa-hourglass-end expiring-peer" data-toggle="tooltip" data-placement="right" title="" data-original-title="Expires at: {{formatDate $p.ExpiresAt}}"></i>{{end}}</td>
|
||||||
<td>{{$p.PublicKey}}</td>
|
<td>{{$p.PublicKey}}</td>
|
||||||
<td>{{$p.Email}}</td>
|
<td>{{$p.Email}}</td>
|
||||||
<td>{{$p.IPsStr}}</td>
|
<td>{{$p.IPsStr}}</td>
|
||||||
@ -102,7 +102,13 @@
|
|||||||
<img class="list-image-large" src="/user/qrcode?pkey={{$p.PublicKey}}"/>
|
<img class="list-image-large" src="/user/qrcode?pkey={{$p.PublicKey}}"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3">
|
<div class="col-md-3">
|
||||||
<div class="float-right mt-5">
|
{{if $p.DeactivatedAt}}
|
||||||
|
<div class="pull-right-lg mt-lg-5 disabled-peer">Peer is disabled! <i class="fas fa-comment-dots" data-toggle="tooltip" data-placement="left" title="" data-original-title="Reason: {{$p.DeactivatedReason}}"></i></div>
|
||||||
|
{{end}}
|
||||||
|
{{if $p.WillExpire}}
|
||||||
|
<div class="pull-right-lg mt-lg-5 expiring-peer"><i class="fas fa-exclamation-triangle"></i> Profile expires on {{ formatDate $p.ExpiresAt}}</div>
|
||||||
|
{{end}}
|
||||||
|
<div class="pull-right-lg mt-lg-5 mt-md-3">
|
||||||
<a href="/user/download?pkey={{$p.PublicKey}}" class="btn btn-primary" title="Download configuration">Download</a>
|
<a href="/user/download?pkey={{$p.PublicKey}}" class="btn btn-primary" title="Download configuration">Download</a>
|
||||||
<a href="/user/email?pkey={{$p.PublicKey}}" class="btn btn-primary" title="Send configuration via Email">Email</a>
|
<a href="/user/email?pkey={{$p.PublicKey}}" class="btn btn-primary" title="Send configuration via Email">Email</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -36,6 +36,14 @@ func init() {
|
|||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
migrations = append(migrations, Migration{
|
||||||
|
version: "1.0.9",
|
||||||
|
migrateFn: func(db *gorm.DB) error {
|
||||||
|
logrus.Infof("upgraded database format to version 1.0.9")
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
type SupportedDatabase string
|
type SupportedDatabase string
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BroadcastAddr returns the last address in the given network, or the broadcast address.
|
// BroadcastAddr returns the last address in the given network, or the broadcast address.
|
||||||
@ -21,7 +22,7 @@ func BroadcastAddr(n *net.IPNet) net.IP {
|
|||||||
return broadcast
|
return broadcast
|
||||||
}
|
}
|
||||||
|
|
||||||
// http://play.golang.org/p/m8TNTtygK0
|
// http://play.golang.org/p/m8TNTtygK0
|
||||||
func IncreaseIP(ip net.IP) {
|
func IncreaseIP(ip net.IP) {
|
||||||
for j := len(ip) - 1; j >= 0; j-- {
|
for j := len(ip) - 1; j >= 0; j-- {
|
||||||
ip[j]++
|
ip[j]++
|
||||||
@ -84,3 +85,11 @@ func ByteCountSI(b int64) string {
|
|||||||
return fmt.Sprintf("%.1f %cB",
|
return fmt.Sprintf("%.1f %cB",
|
||||||
float64(b)/float64(div), "kMGTPE"[exp])
|
float64(b)/float64(div), "kMGTPE"[exp])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func FormatDateHTML(t *time.Time) string {
|
||||||
|
if t == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return t.Format("2006-01-02")
|
||||||
|
}
|
||||||
|
@ -439,6 +439,7 @@ func (s *ApiServer) PutPeer(c *gin.Context) {
|
|||||||
now := time.Now()
|
now := time.Now()
|
||||||
if updatePeer.DeactivatedAt != nil {
|
if updatePeer.DeactivatedAt != nil {
|
||||||
updatePeer.DeactivatedAt = &now
|
updatePeer.DeactivatedAt = &now
|
||||||
|
updatePeer.DeactivatedReason = wireguard.DeactivatedReasonApiEdit
|
||||||
}
|
}
|
||||||
if err := s.s.UpdatePeer(updatePeer, now); err != nil {
|
if err := s.s.UpdatePeer(updatePeer, now); err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, ApiError{Message: err.Error()})
|
c.JSON(http.StatusInternalServerError, ApiError{Message: err.Error()})
|
||||||
@ -516,6 +517,7 @@ func (s *ApiServer) PatchPeer(c *gin.Context) {
|
|||||||
now := time.Now()
|
now := time.Now()
|
||||||
if mergedPeer.DeactivatedAt != nil {
|
if mergedPeer.DeactivatedAt != nil {
|
||||||
mergedPeer.DeactivatedAt = &now
|
mergedPeer.DeactivatedAt = &now
|
||||||
|
mergedPeer.DeactivatedReason = wireguard.DeactivatedReasonApiEdit
|
||||||
}
|
}
|
||||||
if err := s.s.UpdatePeer(mergedPeer, now); err != nil {
|
if err := s.s.UpdatePeer(mergedPeer, now); err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, ApiError{Message: err.Error()})
|
c.JSON(http.StatusInternalServerError, ApiError{Message: err.Error()})
|
||||||
|
@ -67,10 +67,11 @@ type Config struct {
|
|||||||
EditableKeys bool `yaml:"editableKeys" envconfig:"EDITABLE_KEYS"`
|
EditableKeys bool `yaml:"editableKeys" envconfig:"EDITABLE_KEYS"`
|
||||||
CreateDefaultPeer bool `yaml:"createDefaultPeer" envconfig:"CREATE_DEFAULT_PEER"`
|
CreateDefaultPeer bool `yaml:"createDefaultPeer" envconfig:"CREATE_DEFAULT_PEER"`
|
||||||
SelfProvisioningAllowed bool `yaml:"selfProvisioning" envconfig:"SELF_PROVISIONING"`
|
SelfProvisioningAllowed bool `yaml:"selfProvisioning" envconfig:"SELF_PROVISIONING"`
|
||||||
WGExoprterFriendlyNames bool `yaml:"wgExporterFriendlyNames" envconfig:"WG_EXPORTER_FRIENDLY_NAMES"`
|
WGExporterFriendlyNames bool `yaml:"wgExporterFriendlyNames" envconfig:"WG_EXPORTER_FRIENDLY_NAMES"`
|
||||||
LdapEnabled bool `yaml:"ldapEnabled" envconfig:"LDAP_ENABLED"`
|
LdapEnabled bool `yaml:"ldapEnabled" envconfig:"LDAP_ENABLED"`
|
||||||
SessionSecret string `yaml:"sessionSecret" envconfig:"SESSION_SECRET"`
|
SessionSecret string `yaml:"sessionSecret" envconfig:"SESSION_SECRET"`
|
||||||
LogoUrl string `yaml:"logoUrl" envconfig:"LOGO_URL"`
|
LogoUrl string `yaml:"logoUrl" envconfig:"LOGO_URL"`
|
||||||
|
BackgroundTaskInterval int `yaml:"backgroundTaskInterval" envconfig:"BACKGROUND_TASK_INTERVAL"` // in seconds
|
||||||
} `yaml:"core"`
|
} `yaml:"core"`
|
||||||
Database common.DatabaseConfig `yaml:"database"`
|
Database common.DatabaseConfig `yaml:"database"`
|
||||||
Email common.MailConfig `yaml:"email"`
|
Email common.MailConfig `yaml:"email"`
|
||||||
@ -92,8 +93,9 @@ func NewConfig() *Config {
|
|||||||
cfg.Core.AdminPassword = "wgportal"
|
cfg.Core.AdminPassword = "wgportal"
|
||||||
cfg.Core.LdapEnabled = false
|
cfg.Core.LdapEnabled = false
|
||||||
cfg.Core.EditableKeys = true
|
cfg.Core.EditableKeys = true
|
||||||
cfg.Core.WGExoprterFriendlyNames = false
|
cfg.Core.WGExporterFriendlyNames = false
|
||||||
cfg.Core.SessionSecret = "secret"
|
cfg.Core.SessionSecret = "secret"
|
||||||
|
cfg.Core.BackgroundTaskInterval = 15 * 60 // 15 minutes
|
||||||
|
|
||||||
cfg.Database.Typ = "sqlite"
|
cfg.Database.Typ = "sqlite"
|
||||||
cfg.Database.Database = "data/wg_portal.db"
|
cfg.Database.Database = "data/wg_portal.db"
|
||||||
|
@ -1,17 +1,10 @@
|
|||||||
// Package docs GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
|
// Package docs GENERATED BY SWAG; DO NOT EDIT
|
||||||
// This file was generated by swaggo/swag
|
// This file was generated by swaggo/swag
|
||||||
package docs
|
package docs
|
||||||
|
|
||||||
import (
|
import "github.com/swaggo/swag"
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"strings"
|
|
||||||
"text/template"
|
|
||||||
|
|
||||||
"github.com/swaggo/swag"
|
const docTemplate = `{
|
||||||
)
|
|
||||||
|
|
||||||
var doc = `{
|
|
||||||
"schemes": {{ marshal .Schemes }},
|
"schemes": {{ marshal .Schemes }},
|
||||||
"swagger": "2.0",
|
"swagger": "2.0",
|
||||||
"info": {
|
"info": {
|
||||||
@ -1267,10 +1260,13 @@ var doc = `{
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"Mtu": {
|
"Mtu": {
|
||||||
"type": "integer"
|
"type": "integer",
|
||||||
|
"maximum": 1500,
|
||||||
|
"minimum": 0
|
||||||
},
|
},
|
||||||
"PersistentKeepalive": {
|
"PersistentKeepalive": {
|
||||||
"type": "integer"
|
"type": "integer",
|
||||||
|
"minimum": 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -1344,16 +1340,19 @@ var doc = `{
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"DefaultPersistentKeepalive": {
|
"DefaultPersistentKeepalive": {
|
||||||
"type": "integer"
|
"type": "integer",
|
||||||
|
"minimum": 0
|
||||||
},
|
},
|
||||||
"DeviceName": {
|
"DeviceName": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"DisplayName": {
|
"DisplayName": {
|
||||||
"type": "string"
|
"type": "string",
|
||||||
|
"maxLength": 200
|
||||||
},
|
},
|
||||||
"FirewallMark": {
|
"FirewallMark": {
|
||||||
"type": "integer"
|
"type": "integer",
|
||||||
|
"minimum": 0
|
||||||
},
|
},
|
||||||
"IPsStr": {
|
"IPsStr": {
|
||||||
"description": "comma separated list of the IPs of the client, wg-quick addition",
|
"description": "comma separated list of the IPs of the client, wg-quick addition",
|
||||||
@ -1364,7 +1363,9 @@ var doc = `{
|
|||||||
},
|
},
|
||||||
"Mtu": {
|
"Mtu": {
|
||||||
"description": "the interface MTU, wg-quick addition",
|
"description": "the interface MTU, wg-quick addition",
|
||||||
"type": "integer"
|
"type": "integer",
|
||||||
|
"maximum": 1500,
|
||||||
|
"minimum": 0
|
||||||
},
|
},
|
||||||
"PostDown": {
|
"PostDown": {
|
||||||
"description": "post down script, wg-quick addition",
|
"description": "post down script, wg-quick addition",
|
||||||
@ -1399,7 +1400,11 @@ var doc = `{
|
|||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
"Type": {
|
"Type": {
|
||||||
"type": "string"
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"client",
|
||||||
|
"server"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"UpdatedAt": {
|
"UpdatedAt": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
@ -1438,11 +1443,18 @@ var doc = `{
|
|||||||
"DeactivatedAt": {
|
"DeactivatedAt": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"DeactivatedReason": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"DeviceName": {
|
"DeviceName": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"DeviceType": {
|
"DeviceType": {
|
||||||
"type": "string"
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"client",
|
||||||
|
"server"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"Email": {
|
"Email": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
@ -1450,23 +1462,30 @@ var doc = `{
|
|||||||
"Endpoint": {
|
"Endpoint": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"ExpiresAt": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"IPsStr": {
|
"IPsStr": {
|
||||||
"description": "a comma separated list of IPs of the client",
|
"description": "a comma separated list of IPs of the client",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"Identifier": {
|
"Identifier": {
|
||||||
"description": "Identifier AND Email make a WireGuard peer unique",
|
"description": "Identifier AND Email make a WireGuard peer unique",
|
||||||
"type": "string"
|
"type": "string",
|
||||||
|
"maxLength": 64
|
||||||
},
|
},
|
||||||
"IgnoreGlobalSettings": {
|
"IgnoreGlobalSettings": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
"Mtu": {
|
"Mtu": {
|
||||||
"description": "Global Device Settings (can be ignored, only make sense if device is in server mode)",
|
"description": "Global Device Settings (can be ignored, only make sense if device is in server mode)",
|
||||||
"type": "integer"
|
"type": "integer",
|
||||||
|
"maximum": 1500,
|
||||||
|
"minimum": 0
|
||||||
},
|
},
|
||||||
"PersistentKeepalive": {
|
"PersistentKeepalive": {
|
||||||
"type": "integer"
|
"type": "integer",
|
||||||
|
"minimum": 0
|
||||||
},
|
},
|
||||||
"PresharedKey": {
|
"PresharedKey": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
@ -1502,56 +1521,18 @@ var doc = `{
|
|||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
|
|
||||||
type swaggerInfo struct {
|
|
||||||
Version string
|
|
||||||
Host string
|
|
||||||
BasePath string
|
|
||||||
Schemes []string
|
|
||||||
Title string
|
|
||||||
Description string
|
|
||||||
}
|
|
||||||
|
|
||||||
// SwaggerInfo holds exported Swagger Info so clients can modify it
|
// SwaggerInfo holds exported Swagger Info so clients can modify it
|
||||||
var SwaggerInfo = swaggerInfo{
|
var SwaggerInfo = &swag.Spec{
|
||||||
Version: "1.0",
|
Version: "1.0",
|
||||||
Host: "",
|
Host: "",
|
||||||
BasePath: "/api/v1",
|
BasePath: "/api/v1",
|
||||||
Schemes: []string{},
|
Schemes: []string{},
|
||||||
Title: "WireGuard Portal API",
|
Title: "WireGuard Portal API",
|
||||||
Description: "WireGuard Portal API for managing users and peers.",
|
Description: "WireGuard Portal API for managing users and peers.",
|
||||||
}
|
InfoInstanceName: "swagger",
|
||||||
|
SwaggerTemplate: docTemplate,
|
||||||
type s struct{}
|
|
||||||
|
|
||||||
func (s *s) ReadDoc() string {
|
|
||||||
sInfo := SwaggerInfo
|
|
||||||
sInfo.Description = strings.Replace(sInfo.Description, "\n", "\\n", -1)
|
|
||||||
|
|
||||||
t, err := template.New("swagger_info").Funcs(template.FuncMap{
|
|
||||||
"marshal": func(v interface{}) string {
|
|
||||||
a, _ := json.Marshal(v)
|
|
||||||
return string(a)
|
|
||||||
},
|
|
||||||
"escape": func(v interface{}) string {
|
|
||||||
// escape tabs
|
|
||||||
str := strings.Replace(v.(string), "\t", "\\t", -1)
|
|
||||||
// replace " with \", and if that results in \\", replace that with \\\"
|
|
||||||
str = strings.Replace(str, "\"", "\\\"", -1)
|
|
||||||
return strings.Replace(str, "\\\\\"", "\\\\\\\"", -1)
|
|
||||||
},
|
|
||||||
}).Parse(doc)
|
|
||||||
if err != nil {
|
|
||||||
return doc
|
|
||||||
}
|
|
||||||
|
|
||||||
var tpl bytes.Buffer
|
|
||||||
if err := t.Execute(&tpl, sInfo); err != nil {
|
|
||||||
return doc
|
|
||||||
}
|
|
||||||
|
|
||||||
return tpl.String()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
swag.Register(swag.Name, &s{})
|
swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)
|
||||||
}
|
}
|
||||||
|
@ -112,7 +112,7 @@ func (s *Server) GetInterfaceConfig(c *gin.Context) {
|
|||||||
currentSession := GetSessionData(c)
|
currentSession := GetSessionData(c)
|
||||||
device := s.peers.GetDevice(currentSession.DeviceName)
|
device := s.peers.GetDevice(currentSession.DeviceName)
|
||||||
peers := s.peers.GetActivePeers(device.DeviceName)
|
peers := s.peers.GetActivePeers(device.DeviceName)
|
||||||
cfg, err := device.GetConfigFile(peers, s.config.Core.WGExoprterFriendlyNames)
|
cfg, err := device.GetConfigFile(peers, s.config.Core.WGExporterFriendlyNames)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.GetHandleError(c, http.StatusInternalServerError, "ConfigFile error", err.Error())
|
s.GetHandleError(c, http.StatusInternalServerError, "ConfigFile error", err.Error())
|
||||||
return
|
return
|
||||||
|
@ -71,8 +71,18 @@ func (s *Server) PostAdminEditPeer(c *gin.Context) {
|
|||||||
now := time.Now()
|
now := time.Now()
|
||||||
if disabled && currentPeer.DeactivatedAt == nil {
|
if disabled && currentPeer.DeactivatedAt == nil {
|
||||||
formPeer.DeactivatedAt = &now
|
formPeer.DeactivatedAt = &now
|
||||||
|
formPeer.DeactivatedReason = wireguard.DeactivatedReasonAdminEdit
|
||||||
} else if !disabled {
|
} else if !disabled {
|
||||||
formPeer.DeactivatedAt = nil
|
formPeer.DeactivatedAt = nil
|
||||||
|
formPeer.DeactivatedReason = ""
|
||||||
|
// If a peer was deactivated due to expiry, remove the expires-at date to avoid
|
||||||
|
// unwanted re-expiry.
|
||||||
|
if currentPeer.DeactivatedReason == wireguard.DeactivatedReasonExpired {
|
||||||
|
formPeer.ExpiresAt = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if formPeer.ExpiresAt != nil && formPeer.ExpiresAt.IsZero() { // convert 01-01-0001 to nil
|
||||||
|
formPeer.ExpiresAt = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update in database
|
// Update in database
|
||||||
@ -129,6 +139,7 @@ func (s *Server) PostAdminCreatePeer(c *gin.Context) {
|
|||||||
now := time.Now()
|
now := time.Now()
|
||||||
if disabled {
|
if disabled {
|
||||||
formPeer.DeactivatedAt = &now
|
formPeer.DeactivatedAt = &now
|
||||||
|
formPeer.DeactivatedReason = wireguard.DeactivatedReasonAdminCreate
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.CreatePeer(currentSession.DeviceName, formPeer); err != nil {
|
if err := s.CreatePeer(currentSession.DeviceName, formPeer); err != nil {
|
||||||
@ -189,7 +200,7 @@ func (s *Server) PostAdminCreateLdapPeers(c *gin.Context) {
|
|||||||
logrus.Infof("creating %d ldap peers", len(emails))
|
logrus.Infof("creating %d ldap peers", len(emails))
|
||||||
|
|
||||||
for i := range emails {
|
for i := range emails {
|
||||||
if err := s.CreatePeerByEmail(currentSession.DeviceName, emails[i], formData.Identifier, false); err != nil {
|
if err := s.CreatePeerByEmail(currentSession.DeviceName, emails[i], formData.Identifier); err != nil {
|
||||||
_ = s.updateFormInSession(c, formData)
|
_ = s.updateFormInSession(c, formData)
|
||||||
SetFlashMessage(c, "failed to add user: "+err.Error(), "danger")
|
SetFlashMessage(c, "failed to add user: "+err.Error(), "danger")
|
||||||
c.Redirect(http.StatusSeeOther, "/admin/peer/createldap?formerr=create")
|
c.Redirect(http.StatusSeeOther, "/admin/peer/createldap?formerr=create")
|
||||||
@ -440,6 +451,7 @@ func (s *Server) PostUserCreatePeer(c *gin.Context) {
|
|||||||
now := time.Now()
|
now := time.Now()
|
||||||
if disabled {
|
if disabled {
|
||||||
formPeer.DeactivatedAt = &now
|
formPeer.DeactivatedAt = &now
|
||||||
|
formPeer.DeactivatedReason = wireguard.DeactivatedReasonUserCreate
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.CreatePeer(currentSession.DeviceName, formPeer); err != nil {
|
if err := s.CreatePeer(currentSession.DeviceName, formPeer); err != nil {
|
||||||
@ -496,6 +508,7 @@ func (s *Server) PostUserEditPeer(c *gin.Context) {
|
|||||||
now := time.Now()
|
now := time.Now()
|
||||||
if disabled && currentPeer.DeactivatedAt == nil {
|
if disabled && currentPeer.DeactivatedAt == nil {
|
||||||
currentPeer.DeactivatedAt = &now
|
currentPeer.DeactivatedAt = &now
|
||||||
|
currentPeer.DeactivatedReason = wireguard.DeactivatedReasonUserEdit
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update in database
|
// Update in database
|
||||||
|
@ -4,6 +4,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/h44z/wg-portal/internal/wireguard"
|
||||||
|
|
||||||
"github.com/h44z/wg-portal/internal/ldap"
|
"github.com/h44z/wg-portal/internal/ldap"
|
||||||
"github.com/h44z/wg-portal/internal/users"
|
"github.com/h44z/wg-portal/internal/users"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
@ -112,6 +114,7 @@ func (s *Server) disableMissingLdapUsers(ldapUsers []ldap.RawLdapData) {
|
|||||||
for _, peer := range s.peers.GetPeersByMail(activeUsers[i].Email) {
|
for _, peer := range s.peers.GetPeersByMail(activeUsers[i].Email) {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
peer.DeactivatedAt = &now
|
peer.DeactivatedAt = &now
|
||||||
|
peer.DeactivatedReason = wireguard.DeactivatedReasonLdapMissing
|
||||||
if err := s.UpdatePeer(peer, now); err != nil {
|
if err := s.UpdatePeer(peer, now); err != nil {
|
||||||
logrus.Errorf("failed to update deactivated peer %s: %v", peer.PublicKey, err)
|
logrus.Errorf("failed to update deactivated peer %s: %v", peer.PublicKey, err)
|
||||||
}
|
}
|
||||||
@ -141,6 +144,7 @@ func (s *Server) updateLdapUsers(ldapUsers []ldap.RawLdapData) {
|
|||||||
for _, peer := range s.peers.GetPeersByMail(user.Email) {
|
for _, peer := range s.peers.GetPeersByMail(user.Email) {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
peer.DeactivatedAt = nil
|
peer.DeactivatedAt = nil
|
||||||
|
peer.DeactivatedReason = ""
|
||||||
if err = s.UpdatePeer(peer, now); err != nil {
|
if err = s.UpdatePeer(peer, now); err != nil {
|
||||||
logrus.Errorf("failed to update activated peer %s: %v", peer.PublicKey, err)
|
logrus.Errorf("failed to update activated peer %s: %v", peer.PublicKey, err)
|
||||||
}
|
}
|
||||||
|
@ -127,6 +127,7 @@ func (s *Server) Setup(ctx context.Context) error {
|
|||||||
})
|
})
|
||||||
s.server.Use(sessions.Sessions("authsession", cookieStore))
|
s.server.Use(sessions.Sessions("authsession", cookieStore))
|
||||||
s.server.SetFuncMap(template.FuncMap{
|
s.server.SetFuncMap(template.FuncMap{
|
||||||
|
"formatDate": common.FormatDateHTML,
|
||||||
"formatBytes": common.ByteCountSI,
|
"formatBytes": common.ByteCountSI,
|
||||||
"urlEncode": url.QueryEscape,
|
"urlEncode": url.QueryEscape,
|
||||||
"startsWith": strings.HasPrefix,
|
"startsWith": strings.HasPrefix,
|
||||||
@ -215,6 +216,8 @@ func (s *Server) Run() {
|
|||||||
go s.SyncLdapWithUserDatabase()
|
go s.SyncLdapWithUserDatabase()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
go s.RunBackgroundTasks(s.ctx)
|
||||||
|
|
||||||
// Run web service
|
// Run web service
|
||||||
srv := &http.Server{
|
srv := &http.Server{
|
||||||
Addr: s.config.Core.ListeningAddress,
|
Addr: s.config.Core.ListeningAddress,
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@ -62,7 +63,7 @@ func (s *Server) PrepareNewPeer(device string) (wireguard.Peer, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CreatePeerByEmail creates a new peer for the given email.
|
// CreatePeerByEmail creates a new peer for the given email.
|
||||||
func (s *Server) CreatePeerByEmail(device, email, identifierSuffix string, disabled bool) error {
|
func (s *Server) CreatePeerByEmail(device, email, identifierSuffix string) error {
|
||||||
user := s.users.GetUser(email)
|
user := s.users.GetUser(email)
|
||||||
|
|
||||||
peer, err := s.PrepareNewPeer(device)
|
peer, err := s.PrepareNewPeer(device)
|
||||||
@ -75,10 +76,6 @@ func (s *Server) CreatePeerByEmail(device, email, identifierSuffix string, disab
|
|||||||
} else {
|
} else {
|
||||||
peer.Identifier = fmt.Sprintf("%s (%s)", email, identifierSuffix)
|
peer.Identifier = fmt.Sprintf("%s (%s)", email, identifierSuffix)
|
||||||
}
|
}
|
||||||
now := time.Now()
|
|
||||||
if disabled {
|
|
||||||
peer.DeactivatedAt = &now
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.CreatePeer(device, peer)
|
return s.CreatePeer(device, peer)
|
||||||
}
|
}
|
||||||
@ -209,7 +206,7 @@ func (s *Server) WriteWireGuardConfigFile(device string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dev := s.peers.GetDevice(device)
|
dev := s.peers.GetDevice(device)
|
||||||
cfg, err := dev.GetConfigFile(s.peers.GetActivePeers(device), s.config.Core.WGExoprterFriendlyNames)
|
cfg, err := dev.GetConfigFile(s.peers.GetActivePeers(device), s.config.Core.WGExporterFriendlyNames)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithMessage(err, "failed to get config file")
|
return errors.WithMessage(err, "failed to get config file")
|
||||||
}
|
}
|
||||||
@ -281,6 +278,7 @@ func (s *Server) UpdateUser(user users.User) error {
|
|||||||
for _, peer := range s.peers.GetPeersByMail(user.Email) {
|
for _, peer := range s.peers.GetPeersByMail(user.Email) {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
peer.DeactivatedAt = nil
|
peer.DeactivatedAt = nil
|
||||||
|
peer.DeactivatedReason = ""
|
||||||
if err := s.UpdatePeer(peer, now); err != nil {
|
if err := s.UpdatePeer(peer, now); err != nil {
|
||||||
logrus.Errorf("failed to update (re)activated peer %s for %s: %v", peer.PublicKey, user.Email, err)
|
logrus.Errorf("failed to update (re)activated peer %s for %s: %v", peer.PublicKey, user.Email, err)
|
||||||
}
|
}
|
||||||
@ -302,6 +300,7 @@ func (s *Server) DeleteUser(user users.User) error {
|
|||||||
for _, peer := range s.peers.GetPeersByMail(user.Email) {
|
for _, peer := range s.peers.GetPeersByMail(user.Email) {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
peer.DeactivatedAt = &now
|
peer.DeactivatedAt = &now
|
||||||
|
peer.DeactivatedReason = wireguard.DeactivatedReasonUserMissing
|
||||||
if err := s.UpdatePeer(peer, now); err != nil {
|
if err := s.UpdatePeer(peer, now); err != nil {
|
||||||
logrus.Errorf("failed to update deactivated peer %s for %s: %v", peer.PublicKey, user.Email, err)
|
logrus.Errorf("failed to update deactivated peer %s for %s: %v", peer.PublicKey, user.Email, err)
|
||||||
}
|
}
|
||||||
@ -376,3 +375,60 @@ func (s *Server) GetDeviceNames() map[string]string {
|
|||||||
|
|
||||||
return devNames
|
return devNames
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) RunBackgroundTasks(ctx context.Context) {
|
||||||
|
running := true
|
||||||
|
for running {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
running = false
|
||||||
|
continue
|
||||||
|
case <-time.After(time.Duration(s.config.Core.BackgroundTaskInterval) * time.Second):
|
||||||
|
// sleep completed, select will stop blocking
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.Debug("running periodic background tasks...")
|
||||||
|
|
||||||
|
err := s.checkExpiredPeers()
|
||||||
|
if err != nil {
|
||||||
|
logrus.Errorf("failed to check expired peers: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) checkExpiredPeers() error {
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
for _, devName := range s.wg.Cfg.DeviceNames {
|
||||||
|
changed := false
|
||||||
|
peers := s.peers.GetAllPeers(devName)
|
||||||
|
for _, peer := range peers {
|
||||||
|
if peer.IsExpired() && !peer.IsDeactivated() {
|
||||||
|
changed = true
|
||||||
|
|
||||||
|
peer.UpdatedAt = now
|
||||||
|
peer.DeactivatedAt = &now
|
||||||
|
peer.DeactivatedReason = wireguard.DeactivatedReasonExpired
|
||||||
|
|
||||||
|
res := s.db.Save(&peer)
|
||||||
|
if res.Error != nil {
|
||||||
|
return fmt.Errorf("failed save expired peer %s: %w", peer.PublicKey, res.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := s.wg.RemovePeer(peer.DeviceName, peer.PublicKey)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to expire peer %s: %w", peer.PublicKey, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if changed {
|
||||||
|
err := s.WriteWireGuardConfigFile(devName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to persist config for interface %s: %w", devName, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
var Version = "testbuild"
|
var Version = "testbuild"
|
||||||
var DatabaseVersion = "1.0.8"
|
var DatabaseVersion = "1.0.9"
|
||||||
|
@ -23,6 +23,18 @@ import (
|
|||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
DeactivatedReasonExpired = "expired"
|
||||||
|
DeactivatedReasonUserEdit = "user edit action"
|
||||||
|
DeactivatedReasonUserCreate = "user create action"
|
||||||
|
DeactivatedReasonAdminEdit = "admin edit action"
|
||||||
|
DeactivatedReasonAdminCreate = "admin create action"
|
||||||
|
DeactivatedReasonApiEdit = "api edit action"
|
||||||
|
DeactivatedReasonApiCreate = "api create action"
|
||||||
|
DeactivatedReasonLdapMissing = "missing in ldap"
|
||||||
|
DeactivatedReasonUserMissing = "missing user"
|
||||||
|
)
|
||||||
|
|
||||||
// CUSTOM VALIDATORS ----------------------------------------------------------------------------
|
// CUSTOM VALIDATORS ----------------------------------------------------------------------------
|
||||||
var cidrList validator.Func = func(fl validator.FieldLevel) bool {
|
var cidrList validator.Func = func(fl validator.FieldLevel) bool {
|
||||||
cidrListStr := fl.Field().String()
|
cidrListStr := fl.Field().String()
|
||||||
@ -108,11 +120,15 @@ type Peer struct {
|
|||||||
// Global Device Settings (can be ignored, only make sense if device is in server mode)
|
// Global Device Settings (can be ignored, only make sense if device is in server mode)
|
||||||
Mtu int `form:"mtu" binding:"gte=0,lte=1500"`
|
Mtu int `form:"mtu" binding:"gte=0,lte=1500"`
|
||||||
|
|
||||||
DeactivatedAt *time.Time `json:",omitempty"`
|
DeactivatedAt *time.Time `json:",omitempty"`
|
||||||
CreatedBy string
|
DeactivatedReason string `json:",omitempty"`
|
||||||
UpdatedBy string
|
|
||||||
CreatedAt time.Time
|
ExpiresAt *time.Time `json:",omitempty" form:"expires_at" binding:"omitempty" time_format:"2006-01-02"`
|
||||||
UpdatedAt time.Time
|
|
||||||
|
CreatedBy string
|
||||||
|
UpdatedBy string
|
||||||
|
CreatedAt time.Time
|
||||||
|
UpdatedAt time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Peer) SetIPAddresses(addresses ...string) {
|
func (p *Peer) SetIPAddresses(addresses ...string) {
|
||||||
@ -238,6 +254,33 @@ func (p Peer) IsValid() bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p Peer) WillExpire() bool {
|
||||||
|
if p.ExpiresAt == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if p.DeactivatedAt != nil {
|
||||||
|
return false // already deactivated...
|
||||||
|
}
|
||||||
|
if p.ExpiresAt.After(time.Now()) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Peer) IsExpired() bool {
|
||||||
|
if p.ExpiresAt == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if p.ExpiresAt.Before(time.Now()) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Peer) IsDeactivated() bool {
|
||||||
|
return p.DeactivatedAt != nil
|
||||||
|
}
|
||||||
|
|
||||||
func (p Peer) GetConfigFileName() string {
|
func (p Peer) GetConfigFileName() string {
|
||||||
reg := regexp.MustCompile("[^a-zA-Z0-9_-]+")
|
reg := regexp.MustCompile("[^a-zA-Z0-9_-]+")
|
||||||
return reg.ReplaceAllString(strings.ReplaceAll(p.Identifier, " ", "-"), "") + ".conf"
|
return reg.ReplaceAllString(strings.ReplaceAll(p.Identifier, " ", "-"), "") + ".conf"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user