mirror of
https://github.com/h44z/wg-portal.git
synced 2025-12-16 03:26:17 +00:00
Merge branch 'master' into stable
This commit is contained in:
@@ -166,6 +166,9 @@ func NewDatabase(cfg config.DatabaseConfig) (*gorm.DB, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open sqlite database: %w", err)
|
||||
}
|
||||
if err := os.Chmod(cfg.DSN, 0600); err != nil {
|
||||
return nil, fmt.Errorf("failed to set permissions on sqlite database: %w", err)
|
||||
}
|
||||
sqlDB, _ := gormDb.DB()
|
||||
sqlDB.SetMaxOpenConns(1)
|
||||
}
|
||||
|
||||
@@ -2216,7 +2216,9 @@
|
||||
"description": "The source of the user. This field is optional.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"db"
|
||||
"db",
|
||||
"ldap",
|
||||
"oauth"
|
||||
],
|
||||
"example": "db"
|
||||
}
|
||||
|
||||
@@ -561,6 +561,8 @@ definitions:
|
||||
description: The source of the user. This field is optional.
|
||||
enum:
|
||||
- db
|
||||
- ldap
|
||||
- oauth
|
||||
example: db
|
||||
type: string
|
||||
required:
|
||||
|
||||
@@ -48,12 +48,12 @@ func (e InterfaceEndpoint) RegisterRoutes(g *routegroup.Bundle) {
|
||||
apiGroup.Use(e.authenticator.LoggedIn(ScopeAdmin))
|
||||
|
||||
apiGroup.HandleFunc("GET /all", e.handleAllGet())
|
||||
apiGroup.HandleFunc("GET /by-id/{id}", e.handleByIdGet())
|
||||
apiGroup.HandleFunc("GET /by-id/{id...}", e.handleByIdGet())
|
||||
|
||||
apiGroup.HandleFunc("GET /prepare", e.handlePrepareGet())
|
||||
apiGroup.HandleFunc("POST /new", e.handleCreatePost())
|
||||
apiGroup.HandleFunc("PUT /by-id/{id}", e.handleUpdatePut())
|
||||
apiGroup.HandleFunc("DELETE /by-id/{id}", e.handleDelete())
|
||||
apiGroup.HandleFunc("PUT /by-id/{id...}", e.handleUpdatePut())
|
||||
apiGroup.HandleFunc("DELETE /by-id/{id...}", e.handleDelete())
|
||||
}
|
||||
|
||||
// handleAllGet returns a gorm Handler function.
|
||||
|
||||
@@ -44,10 +44,10 @@ func (e MetricsEndpoint) RegisterRoutes(g *routegroup.Bundle) {
|
||||
apiGroup := g.Mount("/metrics")
|
||||
apiGroup.Use(e.authenticator.LoggedIn())
|
||||
|
||||
apiGroup.With(e.authenticator.LoggedIn(ScopeAdmin)).HandleFunc("GET /by-interface/{id}",
|
||||
apiGroup.With(e.authenticator.LoggedIn(ScopeAdmin)).HandleFunc("GET /by-interface/{id...}",
|
||||
e.handleMetricsForInterfaceGet())
|
||||
apiGroup.HandleFunc("GET /by-user/{id}", e.handleMetricsForUserGet())
|
||||
apiGroup.HandleFunc("GET /by-peer/{id}", e.handleMetricsForPeerGet())
|
||||
apiGroup.HandleFunc("GET /by-user/{id...}", e.handleMetricsForUserGet())
|
||||
apiGroup.HandleFunc("GET /by-peer/{id...}", e.handleMetricsForPeerGet())
|
||||
}
|
||||
|
||||
// handleMetricsForInterfaceGet returns a gorm Handler function.
|
||||
|
||||
@@ -47,15 +47,15 @@ func (e PeerEndpoint) RegisterRoutes(g *routegroup.Bundle) {
|
||||
apiGroup := g.Mount("/peer")
|
||||
apiGroup.Use(e.authenticator.LoggedIn())
|
||||
|
||||
apiGroup.With(e.authenticator.LoggedIn(ScopeAdmin)).HandleFunc("GET /by-interface/{id}",
|
||||
apiGroup.With(e.authenticator.LoggedIn(ScopeAdmin)).HandleFunc("GET /by-interface/{id...}",
|
||||
e.handleAllForInterfaceGet())
|
||||
apiGroup.HandleFunc("GET /by-user/{id}", e.handleAllForUserGet())
|
||||
apiGroup.HandleFunc("GET /by-id/{id}", e.handleByIdGet())
|
||||
apiGroup.HandleFunc("GET /by-user/{id...}", e.handleAllForUserGet())
|
||||
apiGroup.HandleFunc("GET /by-id/{id...}", e.handleByIdGet())
|
||||
|
||||
apiGroup.With(e.authenticator.LoggedIn(ScopeAdmin)).HandleFunc("GET /prepare/{id}", e.handlePrepareGet())
|
||||
apiGroup.With(e.authenticator.LoggedIn(ScopeAdmin)).HandleFunc("GET /prepare/{id...}", e.handlePrepareGet())
|
||||
apiGroup.With(e.authenticator.LoggedIn(ScopeAdmin)).HandleFunc("POST /new", e.handleCreatePost())
|
||||
apiGroup.With(e.authenticator.LoggedIn(ScopeAdmin)).HandleFunc("PUT /by-id/{id}", e.handleUpdatePut())
|
||||
apiGroup.With(e.authenticator.LoggedIn(ScopeAdmin)).HandleFunc("DELETE /by-id/{id}", e.handleDelete())
|
||||
apiGroup.With(e.authenticator.LoggedIn(ScopeAdmin)).HandleFunc("PUT /by-id/{id...}", e.handleUpdatePut())
|
||||
apiGroup.With(e.authenticator.LoggedIn(ScopeAdmin)).HandleFunc("DELETE /by-id/{id...}", e.handleDelete())
|
||||
}
|
||||
|
||||
// handleAllForInterfaceGet returns a gorm Handler function.
|
||||
|
||||
@@ -47,10 +47,10 @@ func (e UserEndpoint) RegisterRoutes(g *routegroup.Bundle) {
|
||||
apiGroup.Use(e.authenticator.LoggedIn())
|
||||
|
||||
apiGroup.With(e.authenticator.LoggedIn(ScopeAdmin)).HandleFunc("GET /all", e.handleAllGet())
|
||||
apiGroup.HandleFunc("GET /by-id/{id}", e.handleByIdGet())
|
||||
apiGroup.HandleFunc("GET /by-id/{id...}", e.handleByIdGet())
|
||||
apiGroup.With(e.authenticator.LoggedIn(ScopeAdmin)).HandleFunc("POST /new", e.handleCreatePost())
|
||||
apiGroup.With(e.authenticator.LoggedIn(ScopeAdmin)).HandleFunc("PUT /by-id/{id}", e.handleUpdatePut())
|
||||
apiGroup.With(e.authenticator.LoggedIn(ScopeAdmin)).HandleFunc("DELETE /by-id/{id}", e.handleDelete())
|
||||
apiGroup.With(e.authenticator.LoggedIn(ScopeAdmin)).HandleFunc("PUT /by-id/{id...}", e.handleUpdatePut())
|
||||
apiGroup.With(e.authenticator.LoggedIn(ScopeAdmin)).HandleFunc("DELETE /by-id/{id...}", e.handleDelete())
|
||||
}
|
||||
|
||||
// handleAllGet returns a gorm Handler function.
|
||||
|
||||
@@ -13,7 +13,7 @@ type User struct {
|
||||
// The email address of the user. This field is optional.
|
||||
Email string `json:"Email" binding:"omitempty,email" example:"test@test.com"`
|
||||
// The source of the user. This field is optional.
|
||||
Source string `json:"Source" binding:"oneof=db" example:"db"`
|
||||
Source string `json:"Source" binding:"oneof=db ldap oauth" example:"db"`
|
||||
// The name of the authentication provider. This field is read-only.
|
||||
ProviderName string `json:"ProviderName,omitempty" readonly:"true" example:""`
|
||||
// If this field is set, the user is an admin.
|
||||
|
||||
@@ -20,18 +20,7 @@ type LdapAuthenticator struct {
|
||||
}
|
||||
|
||||
func newLdapAuthenticator(_ context.Context, cfg *config.LdapProvider) (*LdapAuthenticator, error) {
|
||||
var provider = &LdapAuthenticator{}
|
||||
|
||||
provider.cfg = cfg
|
||||
|
||||
dn, err := ldap.ParseDN(cfg.AdminGroupDN)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse admin group DN: %w", err)
|
||||
}
|
||||
provider.cfg.FieldMap = provider.getLdapFieldMapping(cfg.FieldMap)
|
||||
provider.cfg.ParsedAdminGroupDN = dn
|
||||
|
||||
return provider, nil
|
||||
return &LdapAuthenticator{cfg: cfg}, nil
|
||||
}
|
||||
|
||||
// GetName returns the name of the LDAP authenticator.
|
||||
@@ -154,40 +143,3 @@ func (l LdapAuthenticator) ParseUserInfo(raw map[string]any) (*domain.Authentica
|
||||
|
||||
return userInfo, nil
|
||||
}
|
||||
|
||||
func (l LdapAuthenticator) getLdapFieldMapping(f config.LdapFields) config.LdapFields {
|
||||
defaultMap := config.LdapFields{
|
||||
BaseFields: config.BaseFields{
|
||||
UserIdentifier: "mail",
|
||||
Email: "mail",
|
||||
Firstname: "givenName",
|
||||
Lastname: "sn",
|
||||
Phone: "telephoneNumber",
|
||||
Department: "department",
|
||||
},
|
||||
GroupMembership: "memberOf",
|
||||
}
|
||||
if f.UserIdentifier != "" {
|
||||
defaultMap.UserIdentifier = f.UserIdentifier
|
||||
}
|
||||
if f.Email != "" {
|
||||
defaultMap.Email = f.Email
|
||||
}
|
||||
if f.Firstname != "" {
|
||||
defaultMap.Firstname = f.Firstname
|
||||
}
|
||||
if f.Lastname != "" {
|
||||
defaultMap.Lastname = f.Lastname
|
||||
}
|
||||
if f.Phone != "" {
|
||||
defaultMap.Phone = f.Phone
|
||||
}
|
||||
if f.Department != "" {
|
||||
defaultMap.Department = f.Department
|
||||
}
|
||||
if f.GroupMembership != "" {
|
||||
defaultMap.GroupMembership = f.GroupMembership
|
||||
}
|
||||
|
||||
return defaultMap
|
||||
}
|
||||
|
||||
@@ -551,6 +551,12 @@ func (m Manager) updateLdapUsers(
|
||||
return fmt.Errorf("failed to convert LDAP data for %v: %w", rawUser["dn"], err)
|
||||
}
|
||||
|
||||
if provider.SyncLogUserInfo {
|
||||
slog.Debug("ldap user data",
|
||||
"raw-user", rawUser, "user", user.Identifier,
|
||||
"is-admin", user.IsAdmin, "provider", provider.ProviderName)
|
||||
}
|
||||
|
||||
existingUser, err := m.users.GetUser(ctx, user.Identifier)
|
||||
if err != nil && !errors.Is(err, domain.ErrNotFound) {
|
||||
return fmt.Errorf("find error for user id %s: %w", user.Identifier, err)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"regexp"
|
||||
"time"
|
||||
@@ -125,6 +126,45 @@ type LdapFields struct {
|
||||
GroupMembership string `yaml:"memberof"`
|
||||
}
|
||||
|
||||
// getMappingWithDefaults returns a full field mapping for the LDAP provider.
|
||||
// If specific fields are not set, the default values are used.
|
||||
func (f LdapFields) getMappingWithDefaults() LdapFields {
|
||||
defaultMap := LdapFields{
|
||||
BaseFields: BaseFields{
|
||||
UserIdentifier: "mail",
|
||||
Email: "mail",
|
||||
Firstname: "givenName",
|
||||
Lastname: "sn",
|
||||
Phone: "telephoneNumber",
|
||||
Department: "department",
|
||||
},
|
||||
GroupMembership: "memberOf",
|
||||
}
|
||||
if f.UserIdentifier != "" {
|
||||
defaultMap.UserIdentifier = f.UserIdentifier
|
||||
}
|
||||
if f.Email != "" {
|
||||
defaultMap.Email = f.Email
|
||||
}
|
||||
if f.Firstname != "" {
|
||||
defaultMap.Firstname = f.Firstname
|
||||
}
|
||||
if f.Lastname != "" {
|
||||
defaultMap.Lastname = f.Lastname
|
||||
}
|
||||
if f.Phone != "" {
|
||||
defaultMap.Phone = f.Phone
|
||||
}
|
||||
if f.Department != "" {
|
||||
defaultMap.Department = f.Department
|
||||
}
|
||||
if f.GroupMembership != "" {
|
||||
defaultMap.GroupMembership = f.GroupMembership
|
||||
}
|
||||
|
||||
return defaultMap
|
||||
}
|
||||
|
||||
// LdapProvider contains the configuration for the LDAP connection.
|
||||
type LdapProvider struct {
|
||||
// ProviderName is an internal name that is used to distinguish LDAP servers. It must not contain spaces or special characters.
|
||||
@@ -168,6 +208,8 @@ type LdapProvider struct {
|
||||
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"`
|
||||
// If SyncLogUserInfo is set to true, the user info retrieved from the LDAP provider during a sync-run will be logged in trace level.
|
||||
SyncLogUserInfo bool `yaml:"sync_log_user_info"`
|
||||
|
||||
// If RegistrationEnabled is set to true, wg-portal will create new users that do not exist in the database.
|
||||
RegistrationEnabled bool `yaml:"registration_enabled"`
|
||||
@@ -176,6 +218,19 @@ type LdapProvider struct {
|
||||
LogUserInfo bool `yaml:"log_user_info"`
|
||||
}
|
||||
|
||||
// Sanitize checks the LDAP configuration and sets default values for missing fields.
|
||||
func (l *LdapProvider) Sanitize() error {
|
||||
l.FieldMap = l.FieldMap.getMappingWithDefaults()
|
||||
|
||||
dn, err := ldap.ParseDN(l.AdminGroupDN)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse admin group DN: %w", err)
|
||||
}
|
||||
l.ParsedAdminGroupDN = dn
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// OpenIDConnectProvider contains the configuration for the OpenID Connect provider.
|
||||
type OpenIDConnectProvider struct {
|
||||
// ProviderName is an internal name that is used to distinguish oauth endpoints. It must not contain spaces or special characters.
|
||||
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/a8m/envsubst"
|
||||
@@ -114,82 +116,94 @@ func (c *Config) LogStartupValues() {
|
||||
func defaultConfig() *Config {
|
||||
cfg := &Config{}
|
||||
|
||||
cfg.Core.AdminUserDisabled = false
|
||||
cfg.Core.AdminUser = "admin@wgportal.local"
|
||||
cfg.Core.AdminPassword = "wgportal-default"
|
||||
cfg.Core.AdminApiToken = "" // by default, the API access is disabled
|
||||
cfg.Core.ImportExisting = true
|
||||
cfg.Core.RestoreState = true
|
||||
cfg.Core.CreateDefaultPeer = false
|
||||
cfg.Core.CreateDefaultPeerOnCreation = false
|
||||
cfg.Core.EditableKeys = true
|
||||
cfg.Core.SelfProvisioningAllowed = false
|
||||
cfg.Core.ReEnablePeerAfterUserEnable = true
|
||||
cfg.Core.DeletePeerAfterUserDeleted = false
|
||||
cfg.Core.AdminUserDisabled = getEnvBool("WG_PORTAL_CORE_DISABLE_ADMIN_USER", false)
|
||||
cfg.Core.AdminUser = getEnvStr("WG_PORTAL_CORE_ADMIN_USER", "admin@wgportal.local")
|
||||
cfg.Core.AdminPassword = getEnvStr("WG_PORTAL_CORE_ADMIN_PASSWORD", "wgportal-default")
|
||||
cfg.Core.AdminApiToken = getEnvStr("WG_PORTAL_CORE_ADMIN_API_TOKEN", "") // by default, the API access is disabled
|
||||
cfg.Core.ImportExisting = getEnvBool("WG_PORTAL_CORE_IMPORT_EXISTING", true)
|
||||
cfg.Core.RestoreState = getEnvBool("WG_PORTAL_CORE_RESTORE_STATE", true)
|
||||
cfg.Core.CreateDefaultPeer = getEnvBool("WG_PORTAL_CORE_CREATE_DEFAULT_PEER", false)
|
||||
cfg.Core.CreateDefaultPeerOnCreation = getEnvBool("WG_PORTAL_CORE_CREATE_DEFAULT_PEER_ON_CREATION", false)
|
||||
cfg.Core.EditableKeys = getEnvBool("WG_PORTAL_CORE_EDITABLE_KEYS", true)
|
||||
cfg.Core.SelfProvisioningAllowed = getEnvBool("WG_PORTAL_CORE_SELF_PROVISIONING_ALLOWED", false)
|
||||
cfg.Core.ReEnablePeerAfterUserEnable = getEnvBool("WG_PORTAL_CORE_RE_ENABLE_PEER_AFTER_USER_ENABLE", true)
|
||||
cfg.Core.DeletePeerAfterUserDeleted = getEnvBool("WG_PORTAL_CORE_DELETE_PEER_AFTER_USER_DELETED", false)
|
||||
|
||||
cfg.Database = DatabaseConfig{
|
||||
Type: "sqlite",
|
||||
DSN: "data/sqlite.db",
|
||||
Debug: getEnvBool("WG_PORTAL_DATABASE_DEBUG", false),
|
||||
SlowQueryThreshold: getEnvDuration("WG_PORTAL_DATABASE_SLOW_QUERY_THRESHOLD", 0),
|
||||
Type: SupportedDatabase(getEnvStr("WG_PORTAL_DATABASE_TYPE", "sqlite")),
|
||||
DSN: getEnvStr("WG_PORTAL_DATABASE_DSN", "data/sqlite.db"),
|
||||
EncryptionPassphrase: getEnvStr("WG_PORTAL_DATABASE_ENCRYPTION_PASSPHRASE", ""),
|
||||
}
|
||||
|
||||
cfg.Backend = Backend{
|
||||
Default: LocalBackendName, // local backend is the default (using wgcrtl)
|
||||
Default: LocalBackendName, // local backend is the default (using wgcrtl)
|
||||
IgnoredLocalInterfaces: getEnvStrSlice("WG_PORTAL_BACKEND_IGNORED_LOCAL_INTERFACES", nil),
|
||||
// Most resolconf implementations use "tun." as a prefix for interface names.
|
||||
// But systemd's implementation uses no prefix, for example.
|
||||
LocalResolvconfPrefix: "tun.",
|
||||
LocalResolvconfPrefix: getEnvStr("WG_PORTAL_BACKEND_LOCAL_RESOLVCONF_PREFIX", "tun."),
|
||||
}
|
||||
|
||||
cfg.Web = WebConfig{
|
||||
RequestLogging: false,
|
||||
ExternalUrl: "http://localhost:8888",
|
||||
ListeningAddress: ":8888",
|
||||
SessionIdentifier: "wgPortalSession",
|
||||
SessionSecret: "very_secret",
|
||||
CsrfSecret: "extremely_secret",
|
||||
SiteTitle: "WireGuard Portal",
|
||||
SiteCompanyName: "WireGuard Portal",
|
||||
RequestLogging: getEnvBool("WG_PORTAL_WEB_REQUEST_LOGGING", false),
|
||||
ExposeHostInfo: getEnvBool("WG_PORTAL_WEB_EXPOSE_HOST_INFO", false),
|
||||
ExternalUrl: getEnvStr("WG_PORTAL_WEB_EXTERNAL_URL", "http://localhost:8888"),
|
||||
ListeningAddress: getEnvStr("WG_PORTAL_WEB_LISTENING_ADDRESS", ":8888"),
|
||||
SessionIdentifier: getEnvStr("WG_PORTAL_WEB_SESSION_IDENTIFIER", "wgPortalSession"),
|
||||
SessionSecret: getEnvStr("WG_PORTAL_WEB_SESSION_SECRET", "very_secret"),
|
||||
CsrfSecret: getEnvStr("WG_PORTAL_WEB_CSRF_SECRET", "extremely_secret"),
|
||||
SiteTitle: getEnvStr("WG_PORTAL_WEB_SITE_TITLE", "WireGuard Portal"),
|
||||
SiteCompanyName: getEnvStr("WG_PORTAL_WEB_SITE_COMPANY_NAME", "WireGuard Portal"),
|
||||
CertFile: getEnvStr("WG_PORTAL_WEB_CERT_FILE", ""),
|
||||
KeyFile: getEnvStr("WG_PORTAL_WEB_KEY_FILE", ""),
|
||||
}
|
||||
|
||||
cfg.Advanced.LogLevel = "info"
|
||||
cfg.Advanced.StartListenPort = 51820
|
||||
cfg.Advanced.StartCidrV4 = "10.11.12.0/24"
|
||||
cfg.Advanced.StartCidrV6 = "fdfd:d3ad:c0de:1234::0/64"
|
||||
cfg.Advanced.UseIpV6 = true
|
||||
cfg.Advanced.ExpiryCheckInterval = 15 * time.Minute
|
||||
cfg.Advanced.RulePrioOffset = 20000
|
||||
cfg.Advanced.RouteTableOffset = 20000
|
||||
cfg.Advanced.ApiAdminOnly = true
|
||||
cfg.Advanced.LimitAdditionalUserPeers = 0
|
||||
cfg.Advanced.LogLevel = getEnvStr("WG_PORTAL_ADVANCED_LOG_LEVEL", "info")
|
||||
cfg.Advanced.LogPretty = getEnvBool("WG_PORTAL_ADVANCED_LOG_PRETTY", false)
|
||||
cfg.Advanced.LogJson = getEnvBool("WG_PORTAL_ADVANCED_LOG_JSON", false)
|
||||
cfg.Advanced.StartListenPort = getEnvInt("WG_PORTAL_ADVANCED_START_LISTEN_PORT", 51820)
|
||||
cfg.Advanced.StartCidrV4 = getEnvStr("WG_PORTAL_ADVANCED_START_CIDR_V4", "10.11.12.0/24")
|
||||
cfg.Advanced.StartCidrV6 = getEnvStr("WG_PORTAL_ADVANCED_START_CIDR_V6", "fdfd:d3ad:c0de:1234::0/64")
|
||||
cfg.Advanced.UseIpV6 = getEnvBool("WG_PORTAL_ADVANCED_USE_IP_V6", true)
|
||||
cfg.Advanced.ConfigStoragePath = getEnvStr("WG_PORTAL_ADVANCED_CONFIG_STORAGE_PATH", "")
|
||||
cfg.Advanced.ExpiryCheckInterval = getEnvDuration("WG_PORTAL_ADVANCED_EXPIRY_CHECK_INTERVAL", 15*time.Minute)
|
||||
cfg.Advanced.RulePrioOffset = getEnvInt("WG_PORTAL_ADVANCED_RULE_PRIO_OFFSET", 20000)
|
||||
cfg.Advanced.RouteTableOffset = getEnvInt("WG_PORTAL_ADVANCED_ROUTE_TABLE_OFFSET", 20000)
|
||||
cfg.Advanced.ApiAdminOnly = getEnvBool("WG_PORTAL_ADVANCED_API_ADMIN_ONLY", true)
|
||||
cfg.Advanced.LimitAdditionalUserPeers = getEnvInt("WG_PORTAL_ADVANCED_LIMIT_ADDITIONAL_USER_PEERS", 0)
|
||||
|
||||
cfg.Statistics.UsePingChecks = true
|
||||
cfg.Statistics.PingCheckWorkers = 10
|
||||
cfg.Statistics.PingUnprivileged = false
|
||||
cfg.Statistics.PingCheckInterval = 1 * time.Minute
|
||||
cfg.Statistics.DataCollectionInterval = 1 * time.Minute
|
||||
cfg.Statistics.CollectInterfaceData = true
|
||||
cfg.Statistics.CollectPeerData = true
|
||||
cfg.Statistics.CollectAuditData = true
|
||||
cfg.Statistics.ListeningAddress = ":8787"
|
||||
cfg.Statistics.UsePingChecks = getEnvBool("WG_PORTAL_STATISTICS_USE_PING_CHECKS", true)
|
||||
cfg.Statistics.PingCheckWorkers = getEnvInt("WG_PORTAL_STATISTICS_PING_CHECK_WORKERS", 10)
|
||||
cfg.Statistics.PingUnprivileged = getEnvBool("WG_PORTAL_STATISTICS_PING_UNPRIVILEGED", false)
|
||||
cfg.Statistics.PingCheckInterval = getEnvDuration("WG_PORTAL_STATISTICS_PING_CHECK_INTERVAL", 1*time.Minute)
|
||||
cfg.Statistics.DataCollectionInterval = getEnvDuration("WG_PORTAL_STATISTICS_DATA_COLLECTION_INTERVAL",
|
||||
1*time.Minute)
|
||||
cfg.Statistics.CollectInterfaceData = getEnvBool("WG_PORTAL_STATISTICS_COLLECT_INTERFACE_DATA", true)
|
||||
cfg.Statistics.CollectPeerData = getEnvBool("WG_PORTAL_STATISTICS_COLLECT_PEER_DATA", true)
|
||||
cfg.Statistics.CollectAuditData = getEnvBool("WG_PORTAL_STATISTICS_COLLECT_AUDIT_DATA", true)
|
||||
cfg.Statistics.ListeningAddress = getEnvStr("WG_PORTAL_STATISTICS_LISTENING_ADDRESS", ":8787")
|
||||
|
||||
cfg.Mail = MailConfig{
|
||||
Host: "127.0.0.1",
|
||||
Port: 25,
|
||||
Encryption: MailEncryptionNone,
|
||||
CertValidation: true,
|
||||
Username: "",
|
||||
Password: "",
|
||||
AuthType: MailAuthPlain,
|
||||
From: "Wireguard Portal <noreply@wireguard.local>",
|
||||
LinkOnly: false,
|
||||
Host: getEnvStr("WG_PORTAL_MAIL_HOST", "127.0.0.1"),
|
||||
Port: getEnvInt("WG_PORTAL_MAIL_PORT", 25),
|
||||
Encryption: MailEncryption(getEnvStr("WG_PORTAL_MAIL_ENCRYPTION", string(MailEncryptionNone))),
|
||||
CertValidation: getEnvBool("WG_PORTAL_MAIL_CERT_VALIDATION", true),
|
||||
Username: getEnvStr("WG_PORTAL_MAIL_USERNAME", ""),
|
||||
Password: getEnvStr("WG_PORTAL_MAIL_PASSWORD", ""),
|
||||
AuthType: MailAuthType(getEnvStr("WG_PORTAL_MAIL_AUTH_TYPE", string(MailAuthPlain))),
|
||||
From: getEnvStr("WG_PORTAL_MAIL_FROM", "Wireguard Portal <noreply@wireguard.local>"),
|
||||
LinkOnly: getEnvBool("WG_PORTAL_MAIL_LINK_ONLY", false),
|
||||
AllowPeerEmail: getEnvBool("WG_PORTAL_MAIL_ALLOW_PEER_EMAIL", false),
|
||||
}
|
||||
|
||||
cfg.Webhook.Url = "" // no webhook by default
|
||||
cfg.Webhook.Authentication = ""
|
||||
cfg.Webhook.Timeout = 10 * time.Second
|
||||
cfg.Webhook.Url = getEnvStr("WG_PORTAL_WEBHOOK_URL", "") // no webhook by default
|
||||
cfg.Webhook.Authentication = getEnvStr("WG_PORTAL_WEBHOOK_AUTHENTICATION", "")
|
||||
cfg.Webhook.Timeout = getEnvDuration("WG_PORTAL_WEBHOOK_TIMEOUT", 10*time.Second)
|
||||
|
||||
cfg.Auth.WebAuthn.Enabled = true
|
||||
cfg.Auth.MinPasswordLength = 16
|
||||
cfg.Auth.HideLoginForm = false
|
||||
cfg.Auth.WebAuthn.Enabled = getEnvBool("WG_PORTAL_AUTH_WEBAUTHN_ENABLED", true)
|
||||
cfg.Auth.MinPasswordLength = getEnvInt("WG_PORTAL_AUTH_MIN_PASSWORD_LENGTH", 16)
|
||||
cfg.Auth.HideLoginForm = getEnvBool("WG_PORTAL_AUTH_HIDE_LOGIN_FORM", false)
|
||||
|
||||
return cfg
|
||||
}
|
||||
@@ -222,6 +236,11 @@ func GetConfig() (*Config, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i := range cfg.Auth.Ldap {
|
||||
if err := cfg.Auth.Ldap[i].Sanitize(); err != nil {
|
||||
return nil, fmt.Errorf("sanitizing of ldap config for %s failed: %w", cfg.Auth.Ldap[i].ProviderName, err)
|
||||
}
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
@@ -244,3 +263,75 @@ func loadConfigFile(cfg any, filename string) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getEnvStr(name, fallback string) string {
|
||||
if v, ok := os.LookupEnv(name); ok {
|
||||
return v
|
||||
}
|
||||
|
||||
return fallback
|
||||
}
|
||||
|
||||
func getEnvStrSlice(name string, fallback []string) []string {
|
||||
v, ok := os.LookupEnv(name)
|
||||
if !ok {
|
||||
return fallback
|
||||
}
|
||||
|
||||
strParts := strings.Split(v, ",")
|
||||
stringSlice := make([]string, 0, len(strParts))
|
||||
|
||||
for _, s := range strParts {
|
||||
trimmed := strings.TrimSpace(s)
|
||||
if trimmed != "" {
|
||||
stringSlice = append(stringSlice, trimmed)
|
||||
}
|
||||
}
|
||||
|
||||
return stringSlice
|
||||
}
|
||||
|
||||
func getEnvBool(name string, fallback bool) bool {
|
||||
v, ok := os.LookupEnv(name)
|
||||
if !ok {
|
||||
return fallback
|
||||
}
|
||||
|
||||
b, err := strconv.ParseBool(v)
|
||||
if err != nil {
|
||||
slog.Warn("invalid bool env, using fallback", "env", name, "value", v, "fallback", fallback)
|
||||
return fallback
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func getEnvInt(name string, fallback int) int {
|
||||
v, ok := os.LookupEnv(name)
|
||||
if !ok {
|
||||
return fallback
|
||||
}
|
||||
|
||||
i, err := strconv.Atoi(v)
|
||||
if err != nil {
|
||||
slog.Warn("invalid int env, using fallback", "env", name, "value", v, "fallback", fallback)
|
||||
return fallback
|
||||
}
|
||||
|
||||
return i
|
||||
}
|
||||
|
||||
func getEnvDuration(name string, fallback time.Duration) time.Duration {
|
||||
v, ok := os.LookupEnv(name)
|
||||
if !ok {
|
||||
return fallback
|
||||
}
|
||||
|
||||
d, err := time.ParseDuration(v)
|
||||
if err != nil {
|
||||
slog.Warn("invalid duration env, using fallback", "env", name, "value", v, "fallback", fallback)
|
||||
return fallback
|
||||
}
|
||||
|
||||
return d
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user