mirror of
https://github.com/h44z/wg-portal.git
synced 2025-04-19 08:55:12 +00:00
chore: get rid of static code warnings
This commit is contained in:
parent
e24acfa57d
commit
fdb436b135
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -5,7 +5,7 @@ labels: bug
|
|||||||
|
|
||||||
---
|
---
|
||||||
<!-- Tip: you can use code blocks
|
<!-- Tip: you can use code blocks
|
||||||
for better better formatting of yaml config or logs
|
for better formatting of yaml config or logs
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
# config.yaml
|
# config.yaml
|
||||||
|
12
SECURITY.md
12
SECURITY.md
@ -4,17 +4,17 @@ If you believe you've found a security issue in one of the supported versions of
|
|||||||
|
|
||||||
## Supported Versions
|
## Supported Versions
|
||||||
|
|
||||||
| Version | Supported |
|
| Version | Supported |
|
||||||
| ------- | -------------------- |
|
|---------|--------------------|
|
||||||
| v2.x | :white_check_mark: |
|
| v2.x | :white_check_mark: |
|
||||||
| v1.x | :white_check_mark: |
|
| v1.x | :white_check_mark: |
|
||||||
|
|
||||||
## Reporting a Vulnerability
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
Please do not report security vulnerabilities through public GitHub issues.
|
Please do not report security vulnerabilities through public GitHub issues.
|
||||||
|
|
||||||
Instead, we encourage you to submit a report through Github [private vulnerability reporting](https://github.com/h44z/wg-portal/security).
|
Instead, we encourage you to submit a report through GitHub [private vulnerability reporting](https://github.com/h44z/wg-portal/security).
|
||||||
If you prefer to submit a report without logging in to Github, please email *info (at) wgportal.org*.
|
If you prefer to submit a report without logging in to GitHub, please email *info (at) wgportal.org*.
|
||||||
We will respond as soon as possible, but as only two people currently maintain this project, we cannot guarantee specific response times.
|
We will respond as soon as possible, but as only two people currently maintain this project, we cannot guarantee specific response times.
|
||||||
|
|
||||||
We prefer all communications to be in English.
|
We prefer all communications to be in English.
|
||||||
|
@ -57,14 +57,14 @@ func main() {
|
|||||||
cfgFileSystem, err := adapters.NewFileSystemRepository(cfg.Advanced.ConfigStoragePath)
|
cfgFileSystem, err := adapters.NewFileSystemRepository(cfg.Advanced.ConfigStoragePath)
|
||||||
internal.AssertNoError(err)
|
internal.AssertNoError(err)
|
||||||
|
|
||||||
shouldExit, err := app.HandleProgramArgs(cfg, rawDb)
|
shouldExit, err := app.HandleProgramArgs(rawDb)
|
||||||
switch {
|
switch {
|
||||||
case shouldExit && err == nil:
|
case shouldExit && err == nil:
|
||||||
return
|
return
|
||||||
case shouldExit && err != nil:
|
case shouldExit:
|
||||||
logrus.Errorf("Failed to process program args: %v", err)
|
logrus.Errorf("Failed to process program args: %v", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
case !shouldExit:
|
default:
|
||||||
internal.AssertNoError(err)
|
internal.AssertNoError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -356,7 +356,7 @@ Below are the properties for each OIDC provider entry inside `auth.oidc`:
|
|||||||
- Available fields: `user_identifier`, `email`, `firstname`, `lastname`, `phone`, `department`, `is_admin`, `user_groups`.
|
- Available fields: `user_identifier`, `email`, `firstname`, `lastname`, `phone`, `department`, `is_admin`, `user_groups`.
|
||||||
|
|
||||||
| **Field** | **Typical OIDC Claim** | **Explanation** |
|
| **Field** | **Typical OIDC Claim** | **Explanation** |
|
||||||
| ----------------- | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|-------------------|-----------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| `user_identifier` | `sub` or `preferred_username` | A unique identifier for the user. Often the OIDC `sub` claim is used because it’s guaranteed to be unique for the user within the IdP. Some providers also support `preferred_username` if it’s unique. |
|
| `user_identifier` | `sub` or `preferred_username` | A unique identifier for the user. Often the OIDC `sub` claim is used because it’s guaranteed to be unique for the user within the IdP. Some providers also support `preferred_username` if it’s unique. |
|
||||||
| `email` | `email` | The user’s email address as provided by the IdP. Not always verified, depending on IdP settings. |
|
| `email` | `email` | The user’s email address as provided by the IdP. Not always verified, depending on IdP settings. |
|
||||||
| `firstname` | `given_name` | The user’s first name, typically provided by the IdP in the `given_name` claim. |
|
| `firstname` | `given_name` | The user’s first name, typically provided by the IdP in the `given_name` claim. |
|
||||||
@ -425,7 +425,7 @@ Below are the properties for each OAuth provider entry inside `auth.oauth`:
|
|||||||
- Available fields: `user_identifier`, `email`, `firstname`, `lastname`, `phone`, `department`, `is_admin`, `user_groups`.
|
- Available fields: `user_identifier`, `email`, `firstname`, `lastname`, `phone`, `department`, `is_admin`, `user_groups`.
|
||||||
|
|
||||||
| **Field** | **Typical Claim** | **Explanation** |
|
| **Field** | **Typical Claim** | **Explanation** |
|
||||||
| ----------------- | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|-------------------|-----------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| `user_identifier` | `sub` or `preferred_username` | A unique identifier for the user. Often the OIDC `sub` claim is used because it’s guaranteed to be unique for the user within the IdP. Some providers also support `preferred_username` if it’s unique. |
|
| `user_identifier` | `sub` or `preferred_username` | A unique identifier for the user. Often the OIDC `sub` claim is used because it’s guaranteed to be unique for the user within the IdP. Some providers also support `preferred_username` if it’s unique. |
|
||||||
| `email` | `email` | The user’s email address as provided by the IdP. Not always verified, depending on IdP settings. |
|
| `email` | `email` | The user’s email address as provided by the IdP. Not always verified, depending on IdP settings. |
|
||||||
| `firstname` | `given_name` | The user’s first name, typically provided by the IdP in the `given_name` claim. |
|
| `firstname` | `given_name` | The user’s first name, typically provided by the IdP in the `given_name` claim. |
|
||||||
@ -494,7 +494,7 @@ Below are the properties for each LDAP provider entry inside `auth.ldap`:
|
|||||||
- Available fields: `user_identifier`, `email`, `firstname`, `lastname`, `phone`, `department`, `memberof`.
|
- Available fields: `user_identifier`, `email`, `firstname`, `lastname`, `phone`, `department`, `memberof`.
|
||||||
|
|
||||||
| **WireGuard Portal Field** | **Typical LDAP Attribute** | **Short Description** |
|
| **WireGuard Portal Field** | **Typical LDAP Attribute** | **Short Description** |
|
||||||
| -------------------------- | -------------------------- | ------------------------------------------------------------ |
|
|----------------------------|----------------------------|--------------------------------------------------------------|
|
||||||
| user_identifier | sAMAccountName / uid | Uniquely identifies the user within the LDAP directory. |
|
| user_identifier | sAMAccountName / uid | Uniquely identifies the user within the LDAP directory. |
|
||||||
| email | mail / userPrincipalName | Stores the user's primary email address. |
|
| email | mail / userPrincipalName | Stores the user's primary email address. |
|
||||||
| firstname | givenName | Contains the user's first (given) name. |
|
| firstname | givenName | Contains the user's first (given) name. |
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
By default WG-Portal exposes Prometheus metrics on port `8787` if interface/peer statistic data collection is enabled.
|
By default, WG-Portal exposes Prometheus metrics on port `8787` if interface/peer statistic data collection is enabled.
|
||||||
|
|
||||||
## Exposed Metrics
|
## Exposed Metrics
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ import { computed, getCurrentInstance, onMounted, ref } from "vue";
|
|||||||
import { authStore } from "./stores/auth";
|
import { authStore } from "./stores/auth";
|
||||||
import { securityStore } from "./stores/security";
|
import { securityStore } from "./stores/security";
|
||||||
import { settingsStore } from "@/stores/settings";
|
import { settingsStore } from "@/stores/settings";
|
||||||
|
import { Notifications } from "@kyvg/vue3-notification";
|
||||||
|
|
||||||
const appGlobal = getCurrentInstance().appContext.config.globalProperties
|
const appGlobal = getCurrentInstance().appContext.config.globalProperties
|
||||||
const auth = authStore()
|
const auth = authStore()
|
||||||
|
@ -111,6 +111,7 @@ func (l *GormLogger) Trace(ctx context.Context, begin time.Time, fc func() (stri
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewDatabase creates a new database connection and returns a Gorm database instance.
|
||||||
func NewDatabase(cfg config.DatabaseConfig) (*gorm.DB, error) {
|
func NewDatabase(cfg config.DatabaseConfig) (*gorm.DB, error) {
|
||||||
var gormDb *gorm.DB
|
var gormDb *gorm.DB
|
||||||
var err error
|
var err error
|
||||||
@ -172,6 +173,7 @@ type SqlRepo struct {
|
|||||||
db *gorm.DB
|
db *gorm.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewSqlRepository creates a new SqlRepo instance.
|
||||||
func NewSqlRepository(db *gorm.DB) (*SqlRepo, error) {
|
func NewSqlRepository(db *gorm.DB) (*SqlRepo, error) {
|
||||||
repo := &SqlRepo{
|
repo := &SqlRepo{
|
||||||
db: db,
|
db: db,
|
||||||
@ -236,6 +238,8 @@ func (r *SqlRepo) migrate() error {
|
|||||||
|
|
||||||
// region interfaces
|
// region interfaces
|
||||||
|
|
||||||
|
// GetInterface returns the interface with the given id.
|
||||||
|
// If no interface is found, an error domain.ErrNotFound is returned.
|
||||||
func (r *SqlRepo) GetInterface(ctx context.Context, id domain.InterfaceIdentifier) (*domain.Interface, error) {
|
func (r *SqlRepo) GetInterface(ctx context.Context, id domain.InterfaceIdentifier) (*domain.Interface, error) {
|
||||||
var in domain.Interface
|
var in domain.Interface
|
||||||
|
|
||||||
@ -251,6 +255,8 @@ func (r *SqlRepo) GetInterface(ctx context.Context, id domain.InterfaceIdentifie
|
|||||||
return &in, nil
|
return &in, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetInterfaceAndPeers returns the interface with the given id and all peers associated with it.
|
||||||
|
// If no interface is found, an error domain.ErrNotFound is returned.
|
||||||
func (r *SqlRepo) GetInterfaceAndPeers(ctx context.Context, id domain.InterfaceIdentifier) (
|
func (r *SqlRepo) GetInterfaceAndPeers(ctx context.Context, id domain.InterfaceIdentifier) (
|
||||||
*domain.Interface,
|
*domain.Interface,
|
||||||
[]domain.Peer,
|
[]domain.Peer,
|
||||||
@ -269,6 +275,7 @@ func (r *SqlRepo) GetInterfaceAndPeers(ctx context.Context, id domain.InterfaceI
|
|||||||
return in, peers, nil
|
return in, peers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetPeersStats returns the stats for the given peer ids. The order of the returned stats is not guaranteed.
|
||||||
func (r *SqlRepo) GetPeersStats(ctx context.Context, ids ...domain.PeerIdentifier) ([]domain.PeerStatus, error) {
|
func (r *SqlRepo) GetPeersStats(ctx context.Context, ids ...domain.PeerIdentifier) ([]domain.PeerStatus, error) {
|
||||||
if len(ids) == 0 {
|
if len(ids) == 0 {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
@ -284,6 +291,7 @@ func (r *SqlRepo) GetPeersStats(ctx context.Context, ids ...domain.PeerIdentifie
|
|||||||
return stats, nil
|
return stats, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetAllInterfaces returns all interfaces.
|
||||||
func (r *SqlRepo) GetAllInterfaces(ctx context.Context) ([]domain.Interface, error) {
|
func (r *SqlRepo) GetAllInterfaces(ctx context.Context) ([]domain.Interface, error) {
|
||||||
var interfaces []domain.Interface
|
var interfaces []domain.Interface
|
||||||
|
|
||||||
@ -295,6 +303,8 @@ func (r *SqlRepo) GetAllInterfaces(ctx context.Context) ([]domain.Interface, err
|
|||||||
return interfaces, nil
|
return interfaces, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetInterfaceStats returns the stats for the given interface id.
|
||||||
|
// If no stats are found, an error domain.ErrNotFound is returned.
|
||||||
func (r *SqlRepo) GetInterfaceStats(ctx context.Context, id domain.InterfaceIdentifier) (
|
func (r *SqlRepo) GetInterfaceStats(ctx context.Context, id domain.InterfaceIdentifier) (
|
||||||
*domain.InterfaceStatus,
|
*domain.InterfaceStatus,
|
||||||
error,
|
error,
|
||||||
@ -319,6 +329,8 @@ func (r *SqlRepo) GetInterfaceStats(ctx context.Context, id domain.InterfaceIden
|
|||||||
return &stat, nil
|
return &stat, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FindInterfaces returns all interfaces that match the given search string.
|
||||||
|
// The search string is matched against the interface identifier and display name.
|
||||||
func (r *SqlRepo) FindInterfaces(ctx context.Context, search string) ([]domain.Interface, error) {
|
func (r *SqlRepo) FindInterfaces(ctx context.Context, search string) ([]domain.Interface, error) {
|
||||||
var users []domain.Interface
|
var users []domain.Interface
|
||||||
|
|
||||||
@ -335,6 +347,7 @@ func (r *SqlRepo) FindInterfaces(ctx context.Context, search string) ([]domain.I
|
|||||||
return users, nil
|
return users, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SaveInterface updates the interface with the given id.
|
||||||
func (r *SqlRepo) SaveInterface(
|
func (r *SqlRepo) SaveInterface(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
id domain.InterfaceIdentifier,
|
id domain.InterfaceIdentifier,
|
||||||
@ -410,6 +423,7 @@ func (r *SqlRepo) upsertInterface(ui *domain.ContextUserInfo, tx *gorm.DB, in *d
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteInterface deletes the interface with the given id.
|
||||||
func (r *SqlRepo) DeleteInterface(ctx context.Context, id domain.InterfaceIdentifier) error {
|
func (r *SqlRepo) DeleteInterface(ctx context.Context, id domain.InterfaceIdentifier) error {
|
||||||
err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||||
err := tx.Where("interface_identifier = ?", id).Delete(&domain.Peer{}).Error
|
err := tx.Where("interface_identifier = ?", id).Delete(&domain.Peer{}).Error
|
||||||
@ -436,6 +450,7 @@ func (r *SqlRepo) DeleteInterface(ctx context.Context, id domain.InterfaceIdenti
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetInterfaceIps returns a map of interface identifiers to their respective IP addresses.
|
||||||
func (r *SqlRepo) GetInterfaceIps(ctx context.Context) (map[domain.InterfaceIdentifier][]domain.Cidr, error) {
|
func (r *SqlRepo) GetInterfaceIps(ctx context.Context) (map[domain.InterfaceIdentifier][]domain.Cidr, error) {
|
||||||
var ips []struct {
|
var ips []struct {
|
||||||
domain.Cidr
|
domain.Cidr
|
||||||
@ -461,6 +476,8 @@ func (r *SqlRepo) GetInterfaceIps(ctx context.Context) (map[domain.InterfaceIden
|
|||||||
|
|
||||||
// region peers
|
// region peers
|
||||||
|
|
||||||
|
// GetPeer returns the peer with the given id.
|
||||||
|
// If no peer is found, an error domain.ErrNotFound is returned.
|
||||||
func (r *SqlRepo) GetPeer(ctx context.Context, id domain.PeerIdentifier) (*domain.Peer, error) {
|
func (r *SqlRepo) GetPeer(ctx context.Context, id domain.PeerIdentifier) (*domain.Peer, error) {
|
||||||
var peer domain.Peer
|
var peer domain.Peer
|
||||||
|
|
||||||
@ -476,6 +493,7 @@ func (r *SqlRepo) GetPeer(ctx context.Context, id domain.PeerIdentifier) (*domai
|
|||||||
return &peer, nil
|
return &peer, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetInterfacePeers returns all peers associated with the given interface id.
|
||||||
func (r *SqlRepo) GetInterfacePeers(ctx context.Context, id domain.InterfaceIdentifier) ([]domain.Peer, error) {
|
func (r *SqlRepo) GetInterfacePeers(ctx context.Context, id domain.InterfaceIdentifier) ([]domain.Peer, error) {
|
||||||
var peers []domain.Peer
|
var peers []domain.Peer
|
||||||
|
|
||||||
@ -487,6 +505,8 @@ func (r *SqlRepo) GetInterfacePeers(ctx context.Context, id domain.InterfaceIden
|
|||||||
return peers, nil
|
return peers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FindInterfacePeers returns all peers associated with the given interface id that match the given search string.
|
||||||
|
// The search string is matched against the peer identifier, display name and IP address.
|
||||||
func (r *SqlRepo) FindInterfacePeers(ctx context.Context, id domain.InterfaceIdentifier, search string) (
|
func (r *SqlRepo) FindInterfacePeers(ctx context.Context, id domain.InterfaceIdentifier, search string) (
|
||||||
[]domain.Peer,
|
[]domain.Peer,
|
||||||
error,
|
error,
|
||||||
@ -506,6 +526,7 @@ func (r *SqlRepo) FindInterfacePeers(ctx context.Context, id domain.InterfaceIde
|
|||||||
return peers, nil
|
return peers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetUserPeers returns all peers associated with the given user id.
|
||||||
func (r *SqlRepo) GetUserPeers(ctx context.Context, id domain.UserIdentifier) ([]domain.Peer, error) {
|
func (r *SqlRepo) GetUserPeers(ctx context.Context, id domain.UserIdentifier) ([]domain.Peer, error) {
|
||||||
var peers []domain.Peer
|
var peers []domain.Peer
|
||||||
|
|
||||||
@ -517,6 +538,8 @@ func (r *SqlRepo) GetUserPeers(ctx context.Context, id domain.UserIdentifier) ([
|
|||||||
return peers, nil
|
return peers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FindUserPeers returns all peers associated with the given user id that match the given search string.
|
||||||
|
// The search string is matched against the peer identifier, display name and IP address.
|
||||||
func (r *SqlRepo) FindUserPeers(ctx context.Context, id domain.UserIdentifier, search string) ([]domain.Peer, error) {
|
func (r *SqlRepo) FindUserPeers(ctx context.Context, id domain.UserIdentifier, search string) ([]domain.Peer, error) {
|
||||||
var peers []domain.Peer
|
var peers []domain.Peer
|
||||||
|
|
||||||
@ -533,6 +556,8 @@ func (r *SqlRepo) FindUserPeers(ctx context.Context, id domain.UserIdentifier, s
|
|||||||
return peers, nil
|
return peers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SavePeer updates the peer with the given id.
|
||||||
|
// If no existing peer is found, a new peer is created.
|
||||||
func (r *SqlRepo) SavePeer(
|
func (r *SqlRepo) SavePeer(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
id domain.PeerIdentifier,
|
id domain.PeerIdentifier,
|
||||||
@ -607,6 +632,7 @@ func (r *SqlRepo) upsertPeer(ui *domain.ContextUserInfo, tx *gorm.DB, peer *doma
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeletePeer deletes the peer with the given id.
|
||||||
func (r *SqlRepo) DeletePeer(ctx context.Context, id domain.PeerIdentifier) error {
|
func (r *SqlRepo) DeletePeer(ctx context.Context, id domain.PeerIdentifier) error {
|
||||||
err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||||
err := tx.Delete(&domain.PeerStatus{PeerId: id}).Error
|
err := tx.Delete(&domain.PeerStatus{PeerId: id}).Error
|
||||||
@ -628,6 +654,7 @@ func (r *SqlRepo) DeletePeer(ctx context.Context, id domain.PeerIdentifier) erro
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetPeerIps returns a map of peer identifiers to their respective IP addresses.
|
||||||
func (r *SqlRepo) GetPeerIps(ctx context.Context) (map[domain.PeerIdentifier][]domain.Cidr, error) {
|
func (r *SqlRepo) GetPeerIps(ctx context.Context) (map[domain.PeerIdentifier][]domain.Cidr, error) {
|
||||||
var ips []struct {
|
var ips []struct {
|
||||||
domain.Cidr
|
domain.Cidr
|
||||||
@ -649,6 +676,7 @@ func (r *SqlRepo) GetPeerIps(ctx context.Context) (map[domain.PeerIdentifier][]d
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetUsedIpsPerSubnet returns a map of subnets to their respective used IP addresses.
|
||||||
func (r *SqlRepo) GetUsedIpsPerSubnet(ctx context.Context, subnets []domain.Cidr) (
|
func (r *SqlRepo) GetUsedIpsPerSubnet(ctx context.Context, subnets []domain.Cidr) (
|
||||||
map[domain.Cidr][]domain.Cidr,
|
map[domain.Cidr][]domain.Cidr,
|
||||||
error,
|
error,
|
||||||
@ -707,6 +735,8 @@ func (r *SqlRepo) GetUsedIpsPerSubnet(ctx context.Context, subnets []domain.Cidr
|
|||||||
|
|
||||||
// region users
|
// region users
|
||||||
|
|
||||||
|
// GetUser returns the user with the given id.
|
||||||
|
// If no user is found, an error domain.ErrNotFound is returned.
|
||||||
func (r *SqlRepo) GetUser(ctx context.Context, id domain.UserIdentifier) (*domain.User, error) {
|
func (r *SqlRepo) GetUser(ctx context.Context, id domain.UserIdentifier) (*domain.User, error) {
|
||||||
var user domain.User
|
var user domain.User
|
||||||
|
|
||||||
@ -722,6 +752,9 @@ func (r *SqlRepo) GetUser(ctx context.Context, id domain.UserIdentifier) (*domai
|
|||||||
return &user, nil
|
return &user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetUserByEmail returns the user with the given email.
|
||||||
|
// If no user is found, an error domain.ErrNotFound is returned.
|
||||||
|
// If multiple users are found, an error domain.ErrNotUnique is returned.
|
||||||
func (r *SqlRepo) GetUserByEmail(ctx context.Context, email string) (*domain.User, error) {
|
func (r *SqlRepo) GetUserByEmail(ctx context.Context, email string) (*domain.User, error) {
|
||||||
var users []domain.User
|
var users []domain.User
|
||||||
|
|
||||||
@ -746,6 +779,7 @@ func (r *SqlRepo) GetUserByEmail(ctx context.Context, email string) (*domain.Use
|
|||||||
return &user, nil
|
return &user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetAllUsers returns all users.
|
||||||
func (r *SqlRepo) GetAllUsers(ctx context.Context) ([]domain.User, error) {
|
func (r *SqlRepo) GetAllUsers(ctx context.Context) ([]domain.User, error) {
|
||||||
var users []domain.User
|
var users []domain.User
|
||||||
|
|
||||||
@ -757,6 +791,8 @@ func (r *SqlRepo) GetAllUsers(ctx context.Context) ([]domain.User, error) {
|
|||||||
return users, nil
|
return users, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FindUsers returns all users that match the given search string.
|
||||||
|
// The search string is matched against the user identifier, firstname, lastname and email.
|
||||||
func (r *SqlRepo) FindUsers(ctx context.Context, search string) ([]domain.User, error) {
|
func (r *SqlRepo) FindUsers(ctx context.Context, search string) ([]domain.User, error) {
|
||||||
var users []domain.User
|
var users []domain.User
|
||||||
|
|
||||||
@ -774,6 +810,8 @@ func (r *SqlRepo) FindUsers(ctx context.Context, search string) ([]domain.User,
|
|||||||
return users, nil
|
return users, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SaveUser updates the user with the given id.
|
||||||
|
// If no user is found, a new user is created.
|
||||||
func (r *SqlRepo) SaveUser(
|
func (r *SqlRepo) SaveUser(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
id domain.UserIdentifier,
|
id domain.UserIdentifier,
|
||||||
@ -807,6 +845,7 @@ func (r *SqlRepo) SaveUser(
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteUser deletes the user with the given id.
|
||||||
func (r *SqlRepo) DeleteUser(ctx context.Context, id domain.UserIdentifier) error {
|
func (r *SqlRepo) DeleteUser(ctx context.Context, id domain.UserIdentifier) error {
|
||||||
err := r.db.WithContext(ctx).Delete(&domain.User{}, id).Error
|
err := r.db.WithContext(ctx).Delete(&domain.User{}, id).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -859,6 +898,8 @@ func (r *SqlRepo) upsertUser(ui *domain.ContextUserInfo, tx *gorm.DB, user *doma
|
|||||||
|
|
||||||
// region statistics
|
// region statistics
|
||||||
|
|
||||||
|
// UpdateInterfaceStatus updates the interface status with the given id.
|
||||||
|
// If no interface status is found, a new one is created.
|
||||||
func (r *SqlRepo) UpdateInterfaceStatus(
|
func (r *SqlRepo) UpdateInterfaceStatus(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
id domain.InterfaceIdentifier,
|
id domain.InterfaceIdentifier,
|
||||||
@ -919,6 +960,8 @@ func (r *SqlRepo) upsertInterfaceStatus(tx *gorm.DB, in *domain.InterfaceStatus)
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdatePeerStatus updates the peer status with the given id.
|
||||||
|
// If no peer status is found, a new one is created.
|
||||||
func (r *SqlRepo) UpdatePeerStatus(
|
func (r *SqlRepo) UpdatePeerStatus(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
id domain.PeerIdentifier,
|
id domain.PeerIdentifier,
|
||||||
@ -976,6 +1019,7 @@ func (r *SqlRepo) upsertPeerStatus(tx *gorm.DB, in *domain.PeerStatus) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeletePeerStatus deletes the peer status with the given id.
|
||||||
func (r *SqlRepo) DeletePeerStatus(ctx context.Context, id domain.PeerIdentifier) error {
|
func (r *SqlRepo) DeletePeerStatus(ctx context.Context, id domain.PeerIdentifier) error {
|
||||||
err := r.db.WithContext(ctx).Delete(&domain.PeerStatus{}, id).Error
|
err := r.db.WithContext(ctx).Delete(&domain.PeerStatus{}, id).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -989,6 +1033,7 @@ func (r *SqlRepo) DeletePeerStatus(ctx context.Context, id domain.PeerIdentifier
|
|||||||
|
|
||||||
// region audit
|
// region audit
|
||||||
|
|
||||||
|
// SaveAuditEntry saves the given audit entry.
|
||||||
func (r *SqlRepo) SaveAuditEntry(ctx context.Context, entry *domain.AuditEntry) error {
|
func (r *SqlRepo) SaveAuditEntry(ctx context.Context, entry *domain.AuditEntry) error {
|
||||||
err := r.db.WithContext(ctx).Save(entry).Error
|
err := r.db.WithContext(ctx).Save(entry).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -13,6 +13,7 @@ type FilesystemRepo struct {
|
|||||||
basePath string
|
basePath string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewFileSystemRepository creates a new FilesystemRepo instance.
|
||||||
func NewFileSystemRepository(basePath string) (*FilesystemRepo, error) {
|
func NewFileSystemRepository(basePath string) (*FilesystemRepo, error) {
|
||||||
if basePath == "" {
|
if basePath == "" {
|
||||||
return nil, nil // no path, return empty repository
|
return nil, nil // no path, return empty repository
|
||||||
@ -27,6 +28,10 @@ func NewFileSystemRepository(basePath string) (*FilesystemRepo, error) {
|
|||||||
return r, nil
|
return r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WriteFile writes the given contents to the given path.
|
||||||
|
// The path is relative to the base path of the repository.
|
||||||
|
// If the parent directory does not exist, it is created.
|
||||||
|
// If the file already exists, it is overwritten.
|
||||||
func (r *FilesystemRepo) WriteFile(path string, contents io.Reader) error {
|
func (r *FilesystemRepo) WriteFile(path string, contents io.Reader) error {
|
||||||
filePath := filepath.Join(r.basePath, path)
|
filePath := filepath.Join(r.basePath, path)
|
||||||
parentDirectory := filepath.Dir(filePath)
|
parentDirectory := filepath.Dir(filePath)
|
||||||
@ -51,5 +56,4 @@ func (r *FilesystemRepo) WriteFile(path string, contents io.Reader) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -19,11 +19,12 @@ type MailRepo struct {
|
|||||||
cfg *config.MailConfig
|
cfg *config.MailConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewSmtpMailRepo creates a new MailRepo instance.
|
||||||
func NewSmtpMailRepo(cfg config.MailConfig) MailRepo {
|
func NewSmtpMailRepo(cfg config.MailConfig) MailRepo {
|
||||||
return MailRepo{cfg: &cfg}
|
return MailRepo{cfg: &cfg}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send sends a mail.
|
// Send sends a mail using SMTP.
|
||||||
func (r MailRepo) Send(_ context.Context, subject, body string, to []string, options *domain.MailOptions) error {
|
func (r MailRepo) Send(_ context.Context, subject, body string, to []string, options *domain.MailOptions) error {
|
||||||
if options == nil {
|
if options == nil {
|
||||||
options = &domain.MailOptions{}
|
options = &domain.MailOptions{}
|
||||||
|
@ -86,7 +86,7 @@ func NewMetricsServer(cfg *config.Config) *MetricsServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run starts the metrics server
|
// Run starts the metrics server. The function blocks until the context is cancelled.
|
||||||
func (m *MetricsServer) Run(ctx context.Context) {
|
func (m *MetricsServer) Run(ctx context.Context) {
|
||||||
// Run the metrics server in a goroutine
|
// Run the metrics server in a goroutine
|
||||||
go func() {
|
go func() {
|
||||||
@ -104,7 +104,7 @@ func (m *MetricsServer) Run(ctx context.Context) {
|
|||||||
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
// Attempt to gracefully shutdown the metrics server
|
// Attempt to gracefully shut down the metrics server
|
||||||
if err := m.Shutdown(shutdownCtx); err != nil {
|
if err := m.Shutdown(shutdownCtx); err != nil {
|
||||||
logrus.Errorf("metrics service on %s shutdown failed: %v", m.Addr, err)
|
logrus.Errorf("metrics service on %s shutdown failed: %v", m.Addr, err)
|
||||||
} else {
|
} else {
|
||||||
@ -123,9 +123,9 @@ func (m *MetricsServer) UpdateInterfaceMetrics(status domain.InterfaceStatus) {
|
|||||||
func (m *MetricsServer) UpdatePeerMetrics(peer *domain.Peer, status domain.PeerStatus) {
|
func (m *MetricsServer) UpdatePeerMetrics(peer *domain.Peer, status domain.PeerStatus) {
|
||||||
labels := []string{
|
labels := []string{
|
||||||
string(peer.InterfaceIdentifier),
|
string(peer.InterfaceIdentifier),
|
||||||
string(peer.Interface.AddressStr()),
|
peer.Interface.AddressStr(),
|
||||||
string(status.PeerId),
|
string(status.PeerId),
|
||||||
string(peer.DisplayName),
|
peer.DisplayName,
|
||||||
}
|
}
|
||||||
|
|
||||||
if status.LastHandshake != nil {
|
if status.LastHandshake != nil {
|
||||||
|
@ -18,6 +18,7 @@ type WgQuickRepo struct {
|
|||||||
resolvConfIfacePrefix string
|
resolvConfIfacePrefix string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewWgQuickRepo creates a new WgQuickRepo instance.
|
||||||
func NewWgQuickRepo() *WgQuickRepo {
|
func NewWgQuickRepo() *WgQuickRepo {
|
||||||
return &WgQuickRepo{
|
return &WgQuickRepo{
|
||||||
shellCmd: "bash",
|
shellCmd: "bash",
|
||||||
@ -25,6 +26,10 @@ func NewWgQuickRepo() *WgQuickRepo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExecuteInterfaceHook executes the given hook command.
|
||||||
|
// The hook command can contain the following placeholders:
|
||||||
|
//
|
||||||
|
// %i: the interface identifier.
|
||||||
func (r *WgQuickRepo) ExecuteInterfaceHook(id domain.InterfaceIdentifier, hookCmd string) error {
|
func (r *WgQuickRepo) ExecuteInterfaceHook(id domain.InterfaceIdentifier, hookCmd string) error {
|
||||||
if hookCmd == "" {
|
if hookCmd == "" {
|
||||||
return nil
|
return nil
|
||||||
@ -39,6 +44,7 @@ func (r *WgQuickRepo) ExecuteInterfaceHook(id domain.InterfaceIdentifier, hookCm
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetDNS sets the DNS settings for the given interface. It uses resolvconf to set the DNS settings.
|
||||||
func (r *WgQuickRepo) SetDNS(id domain.InterfaceIdentifier, dnsStr, dnsSearchStr string) error {
|
func (r *WgQuickRepo) SetDNS(id domain.InterfaceIdentifier, dnsStr, dnsSearchStr string) error {
|
||||||
if dnsStr == "" && dnsSearchStr == "" {
|
if dnsStr == "" && dnsSearchStr == "" {
|
||||||
return nil
|
return nil
|
||||||
@ -68,6 +74,7 @@ func (r *WgQuickRepo) SetDNS(id domain.InterfaceIdentifier, dnsStr, dnsSearchStr
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UnsetDNS unsets the DNS settings for the given interface. It uses resolvconf to unset the DNS settings.
|
||||||
func (r *WgQuickRepo) UnsetDNS(id domain.InterfaceIdentifier) error {
|
func (r *WgQuickRepo) UnsetDNS(id domain.InterfaceIdentifier) error {
|
||||||
dnsCommand := "resolvconf -d %resPref%i -f"
|
dnsCommand := "resolvconf -d %resPref%i -f"
|
||||||
|
|
||||||
|
@ -20,6 +20,8 @@ type WgRepo struct {
|
|||||||
nl lowlevel.NetlinkClient
|
nl lowlevel.NetlinkClient
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewWireGuardRepository creates a new WgRepo instance.
|
||||||
|
// This repository is used to interact with the WireGuard kernel or userspace module.
|
||||||
func NewWireGuardRepository() *WgRepo {
|
func NewWireGuardRepository() *WgRepo {
|
||||||
wg, err := wgctrl.New()
|
wg, err := wgctrl.New()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -36,6 +38,7 @@ func NewWireGuardRepository() *WgRepo {
|
|||||||
return repo
|
return repo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetInterfaces returns all existing WireGuard interfaces.
|
||||||
func (r *WgRepo) GetInterfaces(_ context.Context) ([]domain.PhysicalInterface, error) {
|
func (r *WgRepo) GetInterfaces(_ context.Context) ([]domain.PhysicalInterface, error) {
|
||||||
devices, err := r.wg.Devices()
|
devices, err := r.wg.Devices()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -54,10 +57,14 @@ func (r *WgRepo) GetInterfaces(_ context.Context) ([]domain.PhysicalInterface, e
|
|||||||
return interfaces, nil
|
return interfaces, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetInterface returns the interface with the given id.
|
||||||
|
// If no interface is found, an error os.ErrNotExist is returned.
|
||||||
func (r *WgRepo) GetInterface(_ context.Context, id domain.InterfaceIdentifier) (*domain.PhysicalInterface, error) {
|
func (r *WgRepo) GetInterface(_ context.Context, id domain.InterfaceIdentifier) (*domain.PhysicalInterface, error) {
|
||||||
return r.getInterface(id)
|
return r.getInterface(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetPeers returns all peers associated with the given interface id.
|
||||||
|
// If the requested interface is found, an error os.ErrNotExist is returned.
|
||||||
func (r *WgRepo) GetPeers(_ context.Context, deviceId domain.InterfaceIdentifier) ([]domain.PhysicalPeer, error) {
|
func (r *WgRepo) GetPeers(_ context.Context, deviceId domain.InterfaceIdentifier) ([]domain.PhysicalPeer, error) {
|
||||||
device, err := r.wg.Device(string(deviceId))
|
device, err := r.wg.Device(string(deviceId))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -76,6 +83,8 @@ func (r *WgRepo) GetPeers(_ context.Context, deviceId domain.InterfaceIdentifier
|
|||||||
return peers, nil
|
return peers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetPeer returns the peer with the given id.
|
||||||
|
// If the requested interface or peer is found, an error os.ErrNotExist is returned.
|
||||||
func (r *WgRepo) GetPeer(
|
func (r *WgRepo) GetPeer(
|
||||||
_ context.Context,
|
_ context.Context,
|
||||||
deviceId domain.InterfaceIdentifier,
|
deviceId domain.InterfaceIdentifier,
|
||||||
@ -157,6 +166,9 @@ func (r *WgRepo) convertWireGuardPeer(peer *wgtypes.Peer) (domain.PhysicalPeer,
|
|||||||
return peerModel, nil
|
return peerModel, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SaveInterface updates the interface with the given id.
|
||||||
|
// If no existing interface is found, a new interface is created.
|
||||||
|
// Updating the interface does not interrupt any existing connections.
|
||||||
func (r *WgRepo) SaveInterface(
|
func (r *WgRepo) SaveInterface(
|
||||||
_ context.Context,
|
_ context.Context,
|
||||||
id domain.InterfaceIdentifier,
|
id domain.InterfaceIdentifier,
|
||||||
@ -187,10 +199,10 @@ func (r *WgRepo) SaveInterface(
|
|||||||
func (r *WgRepo) getOrCreateInterface(id domain.InterfaceIdentifier) (*domain.PhysicalInterface, error) {
|
func (r *WgRepo) getOrCreateInterface(id domain.InterfaceIdentifier) (*domain.PhysicalInterface, error) {
|
||||||
device, err := r.getInterface(id)
|
device, err := r.getInterface(id)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return device, nil
|
return device, nil // interface exists
|
||||||
}
|
}
|
||||||
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
if !errors.Is(err, os.ErrNotExist) {
|
||||||
return nil, fmt.Errorf("device error: %w", err)
|
return nil, fmt.Errorf("device error: %w", err) // unknown error
|
||||||
}
|
}
|
||||||
|
|
||||||
// create new device
|
// create new device
|
||||||
@ -308,6 +320,8 @@ func (r *WgRepo) updateWireGuardInterface(pi *domain.PhysicalInterface) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteInterface deletes the interface with the given id.
|
||||||
|
// If the requested interface is found, no error is returned.
|
||||||
func (r *WgRepo) DeleteInterface(_ context.Context, id domain.InterfaceIdentifier) error {
|
func (r *WgRepo) DeleteInterface(_ context.Context, id domain.InterfaceIdentifier) error {
|
||||||
if err := r.deleteLowLevelInterface(id); err != nil {
|
if err := r.deleteLowLevelInterface(id); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -334,6 +348,8 @@ func (r *WgRepo) deleteLowLevelInterface(id domain.InterfaceIdentifier) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SavePeer updates the peer with the given id.
|
||||||
|
// If no existing peer is found, a new peer is created.
|
||||||
func (r *WgRepo) SavePeer(
|
func (r *WgRepo) SavePeer(
|
||||||
_ context.Context,
|
_ context.Context,
|
||||||
deviceId domain.InterfaceIdentifier,
|
deviceId domain.InterfaceIdentifier,
|
||||||
@ -363,10 +379,10 @@ func (r *WgRepo) getOrCreatePeer(deviceId domain.InterfaceIdentifier, id domain.
|
|||||||
) {
|
) {
|
||||||
peer, err := r.getPeer(deviceId, id)
|
peer, err := r.getPeer(deviceId, id)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return peer, nil
|
return peer, nil // peer exists
|
||||||
}
|
}
|
||||||
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
if !errors.Is(err, os.ErrNotExist) {
|
||||||
return nil, fmt.Errorf("peer error: %w", err)
|
return nil, fmt.Errorf("peer error: %w", err) // unknown error
|
||||||
}
|
}
|
||||||
|
|
||||||
// create new peer
|
// create new peer
|
||||||
@ -425,6 +441,8 @@ func (r *WgRepo) updatePeer(deviceId domain.InterfaceIdentifier, pp *domain.Phys
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeletePeer deletes the peer with the given id.
|
||||||
|
// If the requested interface or peer is found, no error is returned.
|
||||||
func (r *WgRepo) DeletePeer(_ context.Context, deviceId domain.InterfaceIdentifier, id domain.PeerIdentifier) error {
|
func (r *WgRepo) DeletePeer(_ context.Context, deviceId domain.InterfaceIdentifier, id domain.PeerIdentifier) error {
|
||||||
if !id.IsPublicKey() {
|
if !id.IsPublicKey() {
|
||||||
return errors.New("invalid public key")
|
return errors.New("invalid public key")
|
||||||
|
@ -15,6 +15,7 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/h44z/wg-portal/internal"
|
||||||
"github.com/h44z/wg-portal/internal/domain"
|
"github.com/h44z/wg-portal/internal/domain"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -42,12 +43,12 @@ func Test_wgRepository_GetInterfaces(t *testing.T) {
|
|||||||
mgr := setup(t)
|
mgr := setup(t)
|
||||||
|
|
||||||
interfaceName := domain.InterfaceIdentifier("wg_test_001")
|
interfaceName := domain.InterfaceIdentifier("wg_test_001")
|
||||||
defer mgr.DeleteInterface(context.Background(), interfaceName)
|
defer internal.LogError(mgr.DeleteInterface(context.Background(), interfaceName))
|
||||||
err := mgr.SaveInterface(context.Background(), interfaceName, nil)
|
err := mgr.SaveInterface(context.Background(), interfaceName, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
interfaceName2 := domain.InterfaceIdentifier("wg_test_002")
|
interfaceName2 := domain.InterfaceIdentifier("wg_test_002")
|
||||||
defer mgr.DeleteInterface(context.Background(), interfaceName2)
|
defer internal.LogError(mgr.DeleteInterface(context.Background(), interfaceName2))
|
||||||
err = mgr.SaveInterface(context.Background(), interfaceName2, nil)
|
err = mgr.SaveInterface(context.Background(), interfaceName2, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@ -65,7 +66,7 @@ func TestWireGuardCreateInterface(t *testing.T) {
|
|||||||
interfaceName := domain.InterfaceIdentifier("wg_test_001")
|
interfaceName := domain.InterfaceIdentifier("wg_test_001")
|
||||||
ipAddress := "10.11.12.13"
|
ipAddress := "10.11.12.13"
|
||||||
ipV6Address := "1337:d34d:b33f::2"
|
ipV6Address := "1337:d34d:b33f::2"
|
||||||
defer mgr.DeleteInterface(context.Background(), interfaceName)
|
defer internal.LogError(mgr.DeleteInterface(context.Background(), interfaceName))
|
||||||
|
|
||||||
err := mgr.SaveInterface(context.Background(), interfaceName,
|
err := mgr.SaveInterface(context.Background(), interfaceName,
|
||||||
func(pi *domain.PhysicalInterface) (*domain.PhysicalInterface, error) {
|
func(pi *domain.PhysicalInterface) (*domain.PhysicalInterface, error) {
|
||||||
@ -90,7 +91,7 @@ func TestWireGuardUpdateInterface(t *testing.T) {
|
|||||||
mgr := setup(t)
|
mgr := setup(t)
|
||||||
|
|
||||||
interfaceName := domain.InterfaceIdentifier("wg_test_001")
|
interfaceName := domain.InterfaceIdentifier("wg_test_001")
|
||||||
defer mgr.DeleteInterface(context.Background(), interfaceName)
|
defer internal.LogError(mgr.DeleteInterface(context.Background(), interfaceName))
|
||||||
|
|
||||||
err := mgr.SaveInterface(context.Background(), interfaceName, nil)
|
err := mgr.SaveInterface(context.Background(), interfaceName, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -40,7 +40,7 @@ func (e configEndpoint) GetName() string {
|
|||||||
return "ConfigEndpoint"
|
return "ConfigEndpoint"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e configEndpoint) RegisterRoutes(g *gin.RouterGroup, authenticator *authenticationHandler) {
|
func (e configEndpoint) RegisterRoutes(g *gin.RouterGroup, _ *authenticationHandler) {
|
||||||
apiGroup := g.Group("/config")
|
apiGroup := g.Group("/config")
|
||||||
|
|
||||||
apiGroup.GET("/frontend.js", e.handleConfigJsGet())
|
apiGroup.GET("/frontend.js", e.handleConfigJsGet())
|
||||||
|
@ -20,7 +20,7 @@ func (e interfaceEndpoint) GetName() string {
|
|||||||
return "InterfaceEndpoint"
|
return "InterfaceEndpoint"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e interfaceEndpoint) RegisterRoutes(g *gin.RouterGroup, authenticator *authenticationHandler) {
|
func (e interfaceEndpoint) RegisterRoutes(g *gin.RouterGroup, _ *authenticationHandler) {
|
||||||
apiGroup := g.Group("/interface", e.authenticator.LoggedIn(ScopeAdmin))
|
apiGroup := g.Group("/interface", e.authenticator.LoggedIn(ScopeAdmin))
|
||||||
|
|
||||||
apiGroup.GET("/prepare", e.handlePrepareGet())
|
apiGroup.GET("/prepare", e.handlePrepareGet())
|
||||||
|
@ -20,7 +20,7 @@ func (e peerEndpoint) GetName() string {
|
|||||||
return "PeerEndpoint"
|
return "PeerEndpoint"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e peerEndpoint) RegisterRoutes(g *gin.RouterGroup, authenticator *authenticationHandler) {
|
func (e peerEndpoint) RegisterRoutes(g *gin.RouterGroup, _ *authenticationHandler) {
|
||||||
apiGroup := g.Group("/peer", e.authenticator.LoggedIn())
|
apiGroup := g.Group("/peer", e.authenticator.LoggedIn())
|
||||||
|
|
||||||
apiGroup.GET("/iface/:iface/all", e.authenticator.LoggedIn(ScopeAdmin), e.handleAllGet())
|
apiGroup.GET("/iface/:iface/all", e.authenticator.LoggedIn(ScopeAdmin), e.handleAllGet())
|
||||||
|
@ -16,7 +16,7 @@ func (e testEndpoint) GetName() string {
|
|||||||
return "TestEndpoint"
|
return "TestEndpoint"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e testEndpoint) RegisterRoutes(g *gin.RouterGroup, authenticator *authenticationHandler) {
|
func (e testEndpoint) RegisterRoutes(g *gin.RouterGroup, _ *authenticationHandler) {
|
||||||
g.GET("/now", e.handleCurrentTimeGet())
|
g.GET("/now", e.handleCurrentTimeGet())
|
||||||
g.GET("/hostname", e.handleHostnameGet())
|
g.GET("/hostname", e.handleHostnameGet())
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ func (e userEndpoint) GetName() string {
|
|||||||
return "UserEndpoint"
|
return "UserEndpoint"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e userEndpoint) RegisterRoutes(g *gin.RouterGroup, authenticator *authenticationHandler) {
|
func (e userEndpoint) RegisterRoutes(g *gin.RouterGroup, _ *authenticationHandler) {
|
||||||
apiGroup := g.Group("/user", e.authenticator.LoggedIn())
|
apiGroup := g.Group("/user", e.authenticator.LoggedIn())
|
||||||
|
|
||||||
apiGroup.GET("/all", e.authenticator.LoggedIn(ScopeAdmin), e.handleAllGet())
|
apiGroup.GET("/all", e.authenticator.LoggedIn(ScopeAdmin), e.handleAllGet())
|
||||||
|
@ -13,9 +13,7 @@ import (
|
|||||||
type Scope string
|
type Scope string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ScopeAdmin Scope = "ADMIN" // Admin scope contains all other scopes
|
ScopeAdmin Scope = "ADMIN" // Admin scope contains all other scopes
|
||||||
ScopeSwagger Scope = "SWAGGER"
|
|
||||||
ScopeUser Scope = "USER"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type authenticationHandler struct {
|
type authenticationHandler struct {
|
||||||
|
@ -10,13 +10,6 @@ type ConfigOption[T any] struct {
|
|||||||
Overridable bool `json:"Overridable"`
|
Overridable bool `json:"Overridable"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewConfigOption[T any](value T, overridable bool) ConfigOption[T] {
|
|
||||||
return ConfigOption[T]{
|
|
||||||
Value: value,
|
|
||||||
Overridable: overridable,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ConfigOptionFromDomain[T any](opt domain.ConfigOption[T]) ConfigOption[T] {
|
func ConfigOptionFromDomain[T any](opt domain.ConfigOption[T]) ConfigOption[T] {
|
||||||
return ConfigOption[T]{
|
return ConfigOption[T]{
|
||||||
Value: opt.Value,
|
Value: opt.Value,
|
||||||
|
@ -10,13 +10,6 @@ type ConfigOption[T any] struct {
|
|||||||
Overridable bool `json:"Overridable,omitempty"`
|
Overridable bool `json:"Overridable,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewConfigOption[T any](value T, overridable bool) ConfigOption[T] {
|
|
||||||
return ConfigOption[T]{
|
|
||||||
Value: value,
|
|
||||||
Overridable: overridable,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ConfigOptionFromDomain[T any](opt domain.ConfigOption[T]) ConfigOption[T] {
|
func ConfigOptionFromDomain[T any](opt domain.ConfigOption[T]) ConfigOption[T] {
|
||||||
return ConfigOption[T]{
|
return ConfigOption[T]{
|
||||||
Value: opt.Value,
|
Value: opt.Value,
|
||||||
|
@ -8,14 +8,15 @@ import (
|
|||||||
"github.com/h44z/wg-portal/internal/config"
|
"github.com/h44z/wg-portal/internal/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
func HandleProgramArgs(cfg *config.Config, db *gorm.DB) (exit bool, err error) {
|
// HandleProgramArgs handles program arguments and returns true if the program should exit.
|
||||||
|
func HandleProgramArgs(db *gorm.DB) (exit bool, err error) {
|
||||||
migrationSource := flag.String("migrateFrom", "", "path to v1 database file or DSN")
|
migrationSource := flag.String("migrateFrom", "", "path to v1 database file or DSN")
|
||||||
migrationDbType := flag.String("migrateFromType", string(config.DatabaseSQLite),
|
migrationDbType := flag.String("migrateFromType", string(config.DatabaseSQLite),
|
||||||
"old database type, either mysql, mssql, postgres or sqlite")
|
"old database type, either mysql, mssql, postgres or sqlite")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
if *migrationSource != "" {
|
if *migrationSource != "" {
|
||||||
err = migrateFromV1(cfg, db, *migrationSource, *migrationDbType)
|
err = migrateFromV1(db, *migrationSource, *migrationDbType)
|
||||||
exit = true
|
exit = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ import (
|
|||||||
"github.com/h44z/wg-portal/internal/domain"
|
"github.com/h44z/wg-portal/internal/domain"
|
||||||
)
|
)
|
||||||
|
|
||||||
func migrateFromV1(cfg *config.Config, db *gorm.DB, source, typ string) error {
|
func migrateFromV1(db *gorm.DB, source, typ string) error {
|
||||||
sourceType := config.SupportedDatabase(typ)
|
sourceType := config.SupportedDatabase(typ)
|
||||||
switch sourceType {
|
switch sourceType {
|
||||||
case config.DatabaseMySQL, config.DatabasePostgres, config.DatabaseMsSQL:
|
case config.DatabaseMySQL, config.DatabasePostgres, config.DatabaseMsSQL:
|
||||||
|
@ -63,7 +63,7 @@ func (m Manager) connectToMessageBus() {
|
|||||||
_ = m.bus.Subscribe(app.TopicRouteRemove, m.handleRouteRemoveEvent)
|
_ = m.bus.Subscribe(app.TopicRouteRemove, m.handleRouteRemoveEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m Manager) StartBackgroundJobs(ctx context.Context) {
|
func (m Manager) StartBackgroundJobs(_ context.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m Manager) handleRouteUpdateEvent(srcDescription string) {
|
func (m Manager) handleRouteUpdateEvent(srcDescription string) {
|
||||||
@ -124,7 +124,7 @@ func (m Manager) syncRoutes(ctx context.Context) error {
|
|||||||
return fmt.Errorf("failed to find physical link for %s: %w", iface.Identifier, err)
|
return fmt.Errorf("failed to find physical link for %s: %w", iface.Identifier, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
table, fwmark, err := m.getRoutingTableAndFwMark(&iface, allowedIPs, link)
|
table, fwmark, err := m.getRoutingTableAndFwMark(&iface, link)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get table and fwmark for %s: %w", iface.Identifier, err)
|
return fmt.Errorf("failed to get table and fwmark for %s: %w", iface.Identifier, err)
|
||||||
}
|
}
|
||||||
@ -426,11 +426,11 @@ func (m Manager) removeDeprecatedRoutes(link netlink.Link, family int, allowedIP
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m Manager) getRoutingTableAndFwMark(
|
func (m Manager) getRoutingTableAndFwMark(iface *domain.Interface, link netlink.Link) (
|
||||||
iface *domain.Interface,
|
table int,
|
||||||
allowedIPs []domain.Cidr,
|
fwmark uint32,
|
||||||
link netlink.Link,
|
err error,
|
||||||
) (table int, fwmark uint32, err error) {
|
) {
|
||||||
table = iface.GetRoutingTable()
|
table = iface.GetRoutingTable()
|
||||||
fwmark = iface.FirewallMark
|
fwmark = iface.FirewallMark
|
||||||
|
|
||||||
|
@ -71,7 +71,8 @@ func (m Manager) GetAllInterfacesAndPeers(ctx context.Context) ([]domain.Interfa
|
|||||||
|
|
||||||
// GetUserInterfaces returns all interfaces that are available for users to create new peers.
|
// GetUserInterfaces returns all interfaces that are available for users to create new peers.
|
||||||
// If self-provisioning is disabled, this function will return an empty list.
|
// If self-provisioning is disabled, this function will return an empty list.
|
||||||
func (m Manager) GetUserInterfaces(ctx context.Context, id domain.UserIdentifier) ([]domain.Interface, error) {
|
// At the moment, there are no interfaces specific to single users, thus the user id is not used.
|
||||||
|
func (m Manager) GetUserInterfaces(ctx context.Context, _ domain.UserIdentifier) ([]domain.Interface, error) {
|
||||||
if !m.cfg.Core.SelfProvisioningAllowed {
|
if !m.cfg.Core.SelfProvisioningAllowed {
|
||||||
return nil, nil // self-provisioning is disabled - no interfaces for users
|
return nil, nil // self-provisioning is disabled - no interfaces for users
|
||||||
}
|
}
|
||||||
@ -837,7 +838,7 @@ func (m Manager) deleteInterfacePeers(ctx context.Context, id domain.InterfaceId
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m Manager) validateInterfaceModifications(ctx context.Context, old, new *domain.Interface) error {
|
func (m Manager) validateInterfaceModifications(ctx context.Context, _, _ *domain.Interface) error {
|
||||||
currentUser := domain.GetUserInfo(ctx)
|
currentUser := domain.GetUserInfo(ctx)
|
||||||
|
|
||||||
if !currentUser.IsAdmin {
|
if !currentUser.IsAdmin {
|
||||||
@ -847,7 +848,7 @@ func (m Manager) validateInterfaceModifications(ctx context.Context, old, new *d
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m Manager) validateInterfaceCreation(ctx context.Context, old, new *domain.Interface) error {
|
func (m Manager) validateInterfaceCreation(ctx context.Context, _, new *domain.Interface) error {
|
||||||
currentUser := domain.GetUserInfo(ctx)
|
currentUser := domain.GetUserInfo(ctx)
|
||||||
|
|
||||||
if new.Identifier == "" {
|
if new.Identifier == "" {
|
||||||
@ -868,7 +869,7 @@ func (m Manager) validateInterfaceCreation(ctx context.Context, old, new *domain
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m Manager) validateInterfaceDeletion(ctx context.Context, del *domain.Interface) error {
|
func (m Manager) validateInterfaceDeletion(ctx context.Context, _ *domain.Interface) error {
|
||||||
currentUser := domain.GetUserInfo(ctx)
|
currentUser := domain.GetUserInfo(ctx)
|
||||||
|
|
||||||
if !currentUser.IsAdmin {
|
if !currentUser.IsAdmin {
|
||||||
|
@ -475,7 +475,7 @@ func (m Manager) getFreshPeerIpConfig(ctx context.Context, iface *domain.Interfa
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m Manager) validatePeerModifications(ctx context.Context, old, new *domain.Peer) error {
|
func (m Manager) validatePeerModifications(ctx context.Context, _, _ *domain.Peer) error {
|
||||||
currentUser := domain.GetUserInfo(ctx)
|
currentUser := domain.GetUserInfo(ctx)
|
||||||
|
|
||||||
if !currentUser.IsAdmin && !m.cfg.Core.SelfProvisioningAllowed {
|
if !currentUser.IsAdmin && !m.cfg.Core.SelfProvisioningAllowed {
|
||||||
@ -485,7 +485,7 @@ func (m Manager) validatePeerModifications(ctx context.Context, old, new *domain
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m Manager) validatePeerCreation(ctx context.Context, old, new *domain.Peer) error {
|
func (m Manager) validatePeerCreation(ctx context.Context, _, new *domain.Peer) error {
|
||||||
currentUser := domain.GetUserInfo(ctx)
|
currentUser := domain.GetUserInfo(ctx)
|
||||||
|
|
||||||
if new.Identifier == "" {
|
if new.Identifier == "" {
|
||||||
@ -504,7 +504,7 @@ func (m Manager) validatePeerCreation(ctx context.Context, old, new *domain.Peer
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m Manager) validatePeerDeletion(ctx context.Context, del *domain.Peer) error {
|
func (m Manager) validatePeerDeletion(ctx context.Context, _ *domain.Peer) error {
|
||||||
currentUser := domain.GetUserInfo(ctx)
|
currentUser := domain.GetUserInfo(ctx)
|
||||||
|
|
||||||
if !currentUser.IsAdmin && !m.cfg.Core.SelfProvisioningAllowed {
|
if !currentUser.IsAdmin && !m.cfg.Core.SelfProvisioningAllowed {
|
||||||
|
@ -9,24 +9,37 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Auth struct {
|
type Auth struct {
|
||||||
|
// OpenIDConnect contains a list of OpenID Connect providers.
|
||||||
OpenIDConnect []OpenIDConnectProvider `yaml:"oidc"`
|
OpenIDConnect []OpenIDConnectProvider `yaml:"oidc"`
|
||||||
OAuth []OAuthProvider `yaml:"oauth"`
|
// OAuth contains a list of plain OAuth providers.
|
||||||
Ldap []LdapProvider `yaml:"ldap"`
|
OAuth []OAuthProvider `yaml:"oauth"`
|
||||||
|
// Ldap contains a list of LDAP providers.
|
||||||
|
Ldap []LdapProvider `yaml:"ldap"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type BaseFields struct {
|
type BaseFields struct {
|
||||||
|
// UserIdentifier is the name of the field that contains the user identifier.
|
||||||
UserIdentifier string `yaml:"user_identifier"`
|
UserIdentifier string `yaml:"user_identifier"`
|
||||||
Email string `yaml:"email"`
|
// Email is the name of the field that contains the user's email address.
|
||||||
Firstname string `yaml:"firstname"`
|
Email string `yaml:"email"`
|
||||||
Lastname string `yaml:"lastname"`
|
// Firstname is the name of the field that contains the user's first name.
|
||||||
Phone string `yaml:"phone"`
|
Firstname string `yaml:"firstname"`
|
||||||
Department string `yaml:"department"`
|
// Lastname is the name of the field that contains the user's last name.
|
||||||
|
Lastname string `yaml:"lastname"`
|
||||||
|
// Phone is the name of the field that contains the user's phone number.
|
||||||
|
Phone string `yaml:"phone"`
|
||||||
|
// Department is the name of the field that contains the user's department.
|
||||||
|
Department string `yaml:"department"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type OauthFields struct {
|
type OauthFields struct {
|
||||||
BaseFields `yaml:",inline"`
|
BaseFields `yaml:",inline"`
|
||||||
IsAdmin string `yaml:"is_admin"` // If the value is "true", the user is an admin.
|
// IsAdmin is the name of the field that contains the admin flag.
|
||||||
UserGroups string `yaml:"user_groups"` // This value specifies the claim name that contains the users groups.
|
// If the value matches the admin_value_regex, the user is an admin. See OauthAdminMapping for more details.
|
||||||
|
IsAdmin string `yaml:"is_admin"`
|
||||||
|
// UserGroups is the name of the field that contains the user's groups.
|
||||||
|
// If the value matches the admin_group_regex, the user is an admin. See OauthAdminMapping for more details.
|
||||||
|
UserGroups string `yaml:"user_groups"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// OauthAdminMapping contains all necessary information to extract information about administrative privileges
|
// OauthAdminMapping contains all necessary information to extract information about administrative privileges
|
||||||
@ -40,7 +53,7 @@ type OauthAdminMapping struct {
|
|||||||
// If the regex specified in that field matches the contents of the is_admin field, the user is an admin.
|
// If the regex specified in that field matches the contents of the is_admin field, the user is an admin.
|
||||||
AdminValueRegex string `yaml:"admin_value_regex"`
|
AdminValueRegex string `yaml:"admin_value_regex"`
|
||||||
|
|
||||||
// If any of the groups listed in the groups field matches the group specified in the admin_group_regex field, ]
|
// If any of the groups listed in the groups field matches the group specified in the admin_group_regex field,
|
||||||
// the user is an admin.
|
// the user is an admin.
|
||||||
AdminGroupRegex string `yaml:"admin_group_regex"`
|
AdminGroupRegex string `yaml:"admin_group_regex"`
|
||||||
|
|
||||||
@ -50,6 +63,8 @@ type OauthAdminMapping struct {
|
|||||||
adminGroupRegex *regexp.Regexp
|
adminGroupRegex *regexp.Regexp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetAdminValueRegex returns the compiled regular expression for the admin_value_regex field.
|
||||||
|
// If the field is empty, the default value "^true$" is used.
|
||||||
func (o *OauthAdminMapping) GetAdminValueRegex() *regexp.Regexp {
|
func (o *OauthAdminMapping) GetAdminValueRegex() *regexp.Regexp {
|
||||||
if o.adminValueRegex != nil {
|
if o.adminValueRegex != nil {
|
||||||
return o.adminValueRegex // return cached value
|
return o.adminValueRegex // return cached value
|
||||||
@ -69,6 +84,8 @@ func (o *OauthAdminMapping) GetAdminValueRegex() *regexp.Regexp {
|
|||||||
return o.adminValueRegex
|
return o.adminValueRegex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetAdminGroupRegex returns the compiled regular expression for the admin_group_regex field.
|
||||||
|
// If the field is empty, the default value "^wg_portal_default_admin_group$" is used.
|
||||||
func (o *OauthAdminMapping) GetAdminGroupRegex() *regexp.Regexp {
|
func (o *OauthAdminMapping) GetAdminGroupRegex() *regexp.Regexp {
|
||||||
if o.adminGroupRegex != nil {
|
if o.adminGroupRegex != nil {
|
||||||
return o.adminGroupRegex // return cached value
|
return o.adminGroupRegex // return cached value
|
||||||
@ -89,7 +106,8 @@ func (o *OauthAdminMapping) GetAdminGroupRegex() *regexp.Regexp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type LdapFields struct {
|
type LdapFields struct {
|
||||||
BaseFields `yaml:",inline"`
|
BaseFields `yaml:",inline"`
|
||||||
|
// GroupMembership is the name of the LDAP field that contains the groups to which the user belongs.
|
||||||
GroupMembership string `yaml:"memberof"`
|
GroupMembership string `yaml:"memberof"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,27 +115,43 @@ type LdapProvider struct {
|
|||||||
// ProviderName is an internal name that is used to distinguish LDAP servers. It must not contain spaces or special characters.
|
// ProviderName is an internal name that is used to distinguish LDAP servers. It must not contain spaces or special characters.
|
||||||
ProviderName string `yaml:"provider_name"`
|
ProviderName string `yaml:"provider_name"`
|
||||||
|
|
||||||
URL string `yaml:"url"`
|
// URL is the LDAP server URL, e.g. ldap://srv-ad01.company.local:389
|
||||||
StartTLS bool `yaml:"start_tls"`
|
URL string `yaml:"url"`
|
||||||
CertValidation bool `yaml:"cert_validation"`
|
// StartTLS specifies whether STARTTLS should be used to secure the LDAP connection
|
||||||
|
StartTLS bool `yaml:"start_tls"`
|
||||||
|
// CertValidation specifies whether the LDAP server's TLS certificate should be validated
|
||||||
|
CertValidation bool `yaml:"cert_validation"`
|
||||||
|
// TlsCertificatePath is the path to a TLS certificate if needed for LDAP connections
|
||||||
TlsCertificatePath string `yaml:"tls_certificate_path"`
|
TlsCertificatePath string `yaml:"tls_certificate_path"`
|
||||||
TlsKeyPath string `yaml:"tls_key_path"`
|
// TlsKeyPath is the path to the corresponding TLS certificate key
|
||||||
|
TlsKeyPath string `yaml:"tls_key_path"`
|
||||||
|
|
||||||
BaseDN string `yaml:"base_dn"`
|
// BaseDN is the base DN for user searches
|
||||||
|
BaseDN string `yaml:"base_dn"`
|
||||||
|
// BindUser is the bind user for LDAP. It is used to search for users.
|
||||||
BindUser string `yaml:"bind_user"`
|
BindUser string `yaml:"bind_user"`
|
||||||
|
// BindPass is the bind password for LDAP
|
||||||
BindPass string `yaml:"bind_pass"`
|
BindPass string `yaml:"bind_pass"`
|
||||||
|
|
||||||
|
// FieldMap is used to map the names of the LDAP fields to wg-portal fields
|
||||||
FieldMap LdapFields `yaml:"field_map"`
|
FieldMap LdapFields `yaml:"field_map"`
|
||||||
|
|
||||||
LoginFilter string `yaml:"login_filter"` // {{login_identifier}} gets replaced with the login email address / username
|
// LoginFilter is used to select which users can log in.
|
||||||
AdminGroupDN string `yaml:"admin_group"` // Members of this group receive admin rights in WG-Portal
|
// Use the placeholder {{login_identifier}} to insert the username.
|
||||||
|
LoginFilter string `yaml:"login_filter"`
|
||||||
|
// AdminGroupDN is the DN of the group that contains the administrators.
|
||||||
|
// Members of this group receive admin rights in wg-portal
|
||||||
|
AdminGroupDN string `yaml:"admin_group"`
|
||||||
|
// ParsedAdminGroupDN is the parsed version of AdminGroupDN
|
||||||
ParsedAdminGroupDN *ldap.DN `yaml:"-"`
|
ParsedAdminGroupDN *ldap.DN `yaml:"-"`
|
||||||
|
|
||||||
// If DisableMissing is true, missing users will be deactivated
|
// If DisableMissing is true, missing users will be deactivated
|
||||||
DisableMissing bool `yaml:"disable_missing"`
|
DisableMissing bool `yaml:"disable_missing"`
|
||||||
// If AutoReEnable is true, users that where disabled because they were missing will be re-enabled once they are found again
|
// If AutoReEnable is true, users that where disabled because they were missing will be re-enabled once they are found again
|
||||||
AutoReEnable bool `yaml:"auto_re_enable"`
|
AutoReEnable bool `yaml:"auto_re_enable"`
|
||||||
SyncFilter string `yaml:"sync_filter"`
|
// SyncFilter is used to select which users get synchronized into wg-portal
|
||||||
|
SyncFilter string `yaml:"sync_filter"`
|
||||||
|
// SyncInterval is the interval between consecutive LDAP user syncs. If it is 0, sync is disabled.
|
||||||
SyncInterval time.Duration `yaml:"sync_interval"`
|
SyncInterval time.Duration `yaml:"sync_interval"`
|
||||||
|
|
||||||
// If RegistrationEnabled is set to true, wg-portal will create new users that do not exist in the database.
|
// If RegistrationEnabled is set to true, wg-portal will create new users that do not exist in the database.
|
||||||
@ -134,6 +168,7 @@ type OpenIDConnectProvider struct {
|
|||||||
// DisplayName is shown to the user on the login page. If it is empty, ProviderName will be displayed.
|
// DisplayName is shown to the user on the login page. If it is empty, ProviderName will be displayed.
|
||||||
DisplayName string `yaml:"display_name"`
|
DisplayName string `yaml:"display_name"`
|
||||||
|
|
||||||
|
// BaseUrl is the base URL of the OIDC provider.
|
||||||
BaseUrl string `yaml:"base_url"`
|
BaseUrl string `yaml:"base_url"`
|
||||||
|
|
||||||
// ClientID is the application's ID.
|
// ClientID is the application's ID.
|
||||||
@ -172,8 +207,11 @@ type OAuthProvider struct {
|
|||||||
// ClientSecret is the application's secret.
|
// ClientSecret is the application's secret.
|
||||||
ClientSecret string `yaml:"client_secret"`
|
ClientSecret string `yaml:"client_secret"`
|
||||||
|
|
||||||
AuthURL string `yaml:"auth_url"`
|
// AuthURL is the URL to request OAuth user authorization.
|
||||||
TokenURL string `yaml:"token_url"`
|
AuthURL string `yaml:"auth_url"`
|
||||||
|
// TokenURL is the URL to request a token.
|
||||||
|
TokenURL string `yaml:"token_url"`
|
||||||
|
// UserInfoURL is the URL to request user information.
|
||||||
UserInfoURL string `yaml:"user_info_url"`
|
UserInfoURL string `yaml:"user_info_url"`
|
||||||
|
|
||||||
// Scope specifies optional requested permissions.
|
// Scope specifies optional requested permissions.
|
||||||
|
@ -63,6 +63,7 @@ type Config struct {
|
|||||||
Web WebConfig `yaml:"web"`
|
Web WebConfig `yaml:"web"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LogStartupValues logs the startup values of the configuration in debug level
|
||||||
func (c *Config) LogStartupValues() {
|
func (c *Config) LogStartupValues() {
|
||||||
logrus.Infof("Log Level: %s", c.Advanced.LogLevel)
|
logrus.Infof("Log Level: %s", c.Advanced.LogLevel)
|
||||||
|
|
||||||
@ -89,6 +90,7 @@ func (c *Config) LogStartupValues() {
|
|||||||
logrus.Debugf(" - Ldap Providers: %d", len(c.Auth.Ldap))
|
logrus.Debugf(" - Ldap Providers: %d", len(c.Auth.Ldap))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// defaultConfig returns the default configuration
|
||||||
func defaultConfig() *Config {
|
func defaultConfig() *Config {
|
||||||
cfg := &Config{}
|
cfg := &Config{}
|
||||||
|
|
||||||
@ -155,6 +157,8 @@ func defaultConfig() *Config {
|
|||||||
return cfg
|
return cfg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetConfig returns the configuration from the config file.
|
||||||
|
// Environment variable substitution is supported.
|
||||||
func GetConfig() (*Config, error) {
|
func GetConfig() (*Config, error) {
|
||||||
cfg := defaultConfig()
|
cfg := defaultConfig()
|
||||||
|
|
||||||
|
@ -12,8 +12,14 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type DatabaseConfig struct {
|
type DatabaseConfig struct {
|
||||||
Debug bool `yaml:"debug"`
|
// Debug enables logging of all database statements
|
||||||
SlowQueryThreshold time.Duration `yaml:"slow_query_threshold"` // 0 means no logging of slow queries
|
Debug bool `yaml:"debug"`
|
||||||
Type SupportedDatabase `yaml:"type"`
|
// SlowQueryThreshold enables logging of slow queries which take longer than the specified duration
|
||||||
DSN string `yaml:"dsn"` // On SQLite: the database file-path, otherwise the dsn (see: https://gorm.io/docs/connecting_to_the_database.html)
|
SlowQueryThreshold time.Duration `yaml:"slow_query_threshold"` // 0 means no logging of slow queries
|
||||||
|
// Type is the database type. Supported: mysql, mssql, postgres, sqlite
|
||||||
|
Type SupportedDatabase `yaml:"type"`
|
||||||
|
// DSN is the database connection string.
|
||||||
|
// For SQLite, it is the path to the database file.
|
||||||
|
// For other databases, it is the connection string, see: https://gorm.io/docs/connecting_to_the_database.html
|
||||||
|
DSN string `yaml:"dsn"`
|
||||||
}
|
}
|
||||||
|
@ -17,14 +17,23 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type MailConfig struct {
|
type MailConfig struct {
|
||||||
Host string `yaml:"host"`
|
// Host is the hostname or IP of the SMTP server
|
||||||
Port int `yaml:"port"`
|
Host string `yaml:"host"`
|
||||||
Encryption MailEncryption `yaml:"encryption"`
|
// Port is the port number for the SMTP server
|
||||||
CertValidation bool `yaml:"cert_validation"`
|
Port int `yaml:"port"`
|
||||||
Username string `yaml:"username"`
|
// Encryption is the SMTP encryption type
|
||||||
Password string `yaml:"password"`
|
Encryption MailEncryption `yaml:"encryption"`
|
||||||
AuthType MailAuthType `yaml:"auth_type"`
|
// CertValidation specifies whether the SMTP server certificate should be validated
|
||||||
|
CertValidation bool `yaml:"cert_validation"`
|
||||||
|
// Username is the optional SMTP username for authentication
|
||||||
|
Username string `yaml:"username"`
|
||||||
|
// Password is the optional SMTP password for authentication
|
||||||
|
Password string `yaml:"password"`
|
||||||
|
// AuthType is the SMTP authentication type
|
||||||
|
AuthType MailAuthType `yaml:"auth_type"`
|
||||||
|
|
||||||
From string `yaml:"from"`
|
// From is the default "From" address when sending emails
|
||||||
LinkOnly bool `yaml:"link_only"`
|
From string `yaml:"from"`
|
||||||
|
// LinkOnly specifies whether emails should only contain a link to WireGuard Portal or attach the full configuration
|
||||||
|
LinkOnly bool `yaml:"link_only"`
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,25 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
type WebConfig struct {
|
type WebConfig struct {
|
||||||
RequestLogging bool `yaml:"request_logging"`
|
// RequestLogging enables logging of all HTTP requests.
|
||||||
ExternalUrl string `yaml:"external_url"`
|
RequestLogging bool `yaml:"request_logging"`
|
||||||
ListeningAddress string `yaml:"listening_address"`
|
// ExternalUrl is the URL where a client can access WireGuard Portal.
|
||||||
|
// This is used for the callback URL of the OAuth providers.
|
||||||
|
ExternalUrl string `yaml:"external_url"`
|
||||||
|
// ListeningAddress is the address and port for the web server.
|
||||||
|
ListeningAddress string `yaml:"listening_address"`
|
||||||
|
// SessionIdentifier is the session identifier for the web frontend.
|
||||||
SessionIdentifier string `yaml:"session_identifier"`
|
SessionIdentifier string `yaml:"session_identifier"`
|
||||||
SessionSecret string `yaml:"session_secret"`
|
// SessionSecret is the session secret for the web frontend.
|
||||||
CsrfSecret string `yaml:"csrf_secret"`
|
SessionSecret string `yaml:"session_secret"`
|
||||||
SiteTitle string `yaml:"site_title"`
|
// CsrfSecret is the CSRF secret.
|
||||||
SiteCompanyName string `yaml:"site_company_name"`
|
CsrfSecret string `yaml:"csrf_secret"`
|
||||||
CertFile string `yaml:"cert_file"`
|
// SiteTitle is the title that is shown in the web frontend.
|
||||||
KeyFile string `yaml:"key_file"`
|
SiteTitle string `yaml:"site_title"`
|
||||||
|
// SiteCompanyName is the company name that is shown at the bottom of the web frontend.
|
||||||
|
SiteCompanyName string `yaml:"site_company_name"`
|
||||||
|
// CertFile is the path to the TLS certificate file.
|
||||||
|
CertFile string `yaml:"cert_file"`
|
||||||
|
// KeyFile is the path to the TLS certificate key file.
|
||||||
|
KeyFile string `yaml:"key_file"`
|
||||||
}
|
}
|
||||||
|
@ -5,8 +5,6 @@ import "time"
|
|||||||
type AuditSeverityLevel string
|
type AuditSeverityLevel string
|
||||||
|
|
||||||
const AuditSeverityLevelLow AuditSeverityLevel = "low"
|
const AuditSeverityLevelLow AuditSeverityLevel = "low"
|
||||||
const AuditSeverityLevelMedium AuditSeverityLevel = "medium"
|
|
||||||
const AuditSeverityLevelHigh AuditSeverityLevel = "high"
|
|
||||||
|
|
||||||
type AuditEntry struct {
|
type AuditEntry struct {
|
||||||
UniqueId uint64 `gorm:"primaryKey;autoIncrement:true;column:id"`
|
UniqueId uint64 `gorm:"primaryKey;autoIncrement:true;column:id"`
|
||||||
|
@ -42,7 +42,7 @@ func (ps *PrivateString) Scan(value any) error {
|
|||||||
case string:
|
case string:
|
||||||
*ps = PrivateString(v)
|
*ps = PrivateString(v)
|
||||||
case []byte:
|
case []byte:
|
||||||
*ps = PrivateString(string(v))
|
*ps = PrivateString(v)
|
||||||
default:
|
default:
|
||||||
return errors.New("invalid type for PrivateString")
|
return errors.New("invalid type for PrivateString")
|
||||||
}
|
}
|
||||||
@ -57,7 +57,6 @@ const (
|
|||||||
DisabledReasonAdmin = "disabled by admin"
|
DisabledReasonAdmin = "disabled by admin"
|
||||||
DisabledReasonApi = "disabled through api"
|
DisabledReasonApi = "disabled through api"
|
||||||
DisabledReasonLdapMissing = "missing in ldap"
|
DisabledReasonLdapMissing = "missing in ldap"
|
||||||
DisabledReasonUserMissing = "missing user"
|
|
||||||
DisabledReasonMigrationDummy = "migration dummy user"
|
DisabledReasonMigrationDummy = "migration dummy user"
|
||||||
DisabledReasonInterfaceMissing = "missing WireGuard interface"
|
DisabledReasonInterfaceMissing = "missing WireGuard interface"
|
||||||
|
|
||||||
|
@ -19,6 +19,22 @@ func LogClose(c io.Closer) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LogError logs the given error if it is not nil.
|
||||||
|
// If a message is given, it is prepended to the error message.
|
||||||
|
// Only the first message is used.
|
||||||
|
func LogError(err error, msg ...string) {
|
||||||
|
if err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(msg) > 0 {
|
||||||
|
logrus.Errorf("%s: %v", msg[0], err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
logrus.Errorf("error: %v", err)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// SignalAwareContext returns a context that gets closed once a given signal is retrieved.
|
// SignalAwareContext returns a context that gets closed once a given signal is retrieved.
|
||||||
// By default, the following signals are handled: syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP
|
// By default, the following signals are handled: syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP
|
||||||
func SignalAwareContext(ctx context.Context, sig ...os.Signal) context.Context {
|
func SignalAwareContext(ctx context.Context, sig ...os.Signal) context.Context {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user