V2 alpha - initial version (#172)

Initial alpha codebase for version 2 of WireGuard Portal.
This version is considered unstable and incomplete (for example, no public REST API)! 
Use with care!


Fixes/Implements the following issues:
 - OAuth support #154, #1 
 - New Web UI with internationalisation support #98, #107, #89, #62
 - Postgres Support #49 
 - Improved Email handling #47, #119 
 - DNS Search Domain support #46 
 - Bugfixes #94, #48 

---------

Co-authored-by: Fabian Wechselberger <wechselbergerf@hotmail.com>
This commit is contained in:
h44z
2023-08-04 13:34:18 +02:00
committed by GitHub
parent b3a5f2ac60
commit 8b820a5adf
788 changed files with 46139 additions and 11281 deletions

118
internal/config/auth.go Normal file
View File

@@ -0,0 +1,118 @@
package config
import (
"github.com/go-ldap/ldap/v3"
)
type Auth struct {
OpenIDConnect []OpenIDConnectProvider `yaml:"oidc"`
OAuth []OAuthProvider `yaml:"oauth"`
Ldap []LdapProvider `yaml:"ldap"`
CallbackUrlPrefix string `yaml:"callback_url_prefix"`
}
type BaseFields struct {
UserIdentifier string `yaml:"user_identifier"`
Email string `yaml:"email"`
Firstname string `yaml:"firstname"`
Lastname string `yaml:"lastname"`
Phone string `yaml:"phone"`
Department string `yaml:"department"`
}
type OauthFields struct {
BaseFields `yaml:",inline"`
IsAdmin string `yaml:"is_admin"`
}
type LdapFields struct {
BaseFields `yaml:",inline"`
GroupMembership string `yaml:"memberof"`
}
type LdapProvider struct {
// 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"`
URL string `yaml:"url"`
StartTLS bool `yaml:"start_tls"`
CertValidation bool `yaml:"cert_validation"`
TlsCertificatePath string `yaml:"tls_certificate_path"`
TlsKeyPath string `yaml:"tls_key_path"`
BaseDN string `yaml:"base_dn"`
BindUser string `yaml:"bind_user"`
BindPass string `yaml:"bind_pass"`
FieldMap LdapFields `yaml:"field_map"`
LoginFilter string `yaml:"login_filter"` // {{login_identifier}} gets replaced with the login email address / username
AdminGroupDN string `yaml:"admin_group"` // Members of this group receive admin rights in WG-Portal
ParsedAdminGroupDN *ldap.DN `yaml:"-"`
Synchronize bool `yaml:"synchronize"`
// If DisableMissing is false, missing users will be deactivated
DisableMissing bool `yaml:"disable_missing"`
SyncFilter string `yaml:"sync_filter"`
// If RegistrationEnabled is set to true, wg-portal will create new users that do not exist in the database.
RegistrationEnabled bool `yaml:"registration_enabled"`
}
type OpenIDConnectProvider struct {
// ProviderName is an internal name that is used to distinguish oauth endpoints. It must not contain spaces or special characters.
ProviderName string `yaml:"provider_name"`
// DisplayName is shown to the user on the login page. If it is empty, ProviderName will be displayed.
DisplayName string `yaml:"display_name"`
BaseUrl string `yaml:"base_url"`
// ClientID is the application's ID.
ClientID string `yaml:"client_id"`
// ClientSecret is the application's secret.
ClientSecret string `yaml:"client_secret"`
// ExtraScopes specifies optional requested permissions.
ExtraScopes []string `yaml:"extra_scopes"`
// FieldMap is used to map the names of the user-info endpoint fields to wg-portal fields
FieldMap OauthFields `yaml:"field_map"`
// If RegistrationEnabled is set to true, missing users will be created in the database
RegistrationEnabled bool `yaml:"registration_enabled"`
}
type OAuthProvider struct {
// ProviderName is an internal name that is used to distinguish oauth endpoints. It must not contain spaces or special characters.
ProviderName string `yaml:"provider_name"`
// DisplayName is shown to the user on the login page. If it is empty, ProviderName will be displayed.
DisplayName string `yaml:"display_name"`
BaseUrl string `yaml:"base_url"`
// ClientID is the application's ID.
ClientID string `yaml:"client_id"`
// ClientSecret is the application's secret.
ClientSecret string `yaml:"client_secret"`
AuthURL string `yaml:"auth_url"`
TokenURL string `yaml:"token_url"`
UserInfoURL string `yaml:"user_info_url"`
// RedirectURL is the URL to redirect users going through
// the OAuth flow, after the resource owner's URLs.
RedirectURL string `yaml:"redirect_url"`
// Scope specifies optional requested permissions.
Scopes []string `yaml:"scopes"`
// FieldMap is used to map the names of the user-info endpoint fields to wg-portal fields
FieldMap OauthFields `yaml:"field_map"`
// If RegistrationEnabled is set to true, wg-portal will create new users that do not exist in the database.
RegistrationEnabled bool `yaml:"registration_enabled"`
}

173
internal/config/config.go Normal file
View File

@@ -0,0 +1,173 @@
package config
import (
"fmt"
"github.com/sirupsen/logrus"
"os"
"time"
"gopkg.in/yaml.v2"
)
type Config struct {
Core struct {
// AdminUser defines the default administrator account that will be created
AdminUser string `yaml:"admin_user"`
AdminPassword string `yaml:"admin_password"`
EditableKeys bool `yaml:"editable_keys"`
CreateDefaultPeer bool `yaml:"create_default_peer"`
SelfProvisioningAllowed bool `yaml:"self_provisioning_allowed"`
ImportExisting bool `yaml:"import_existing"`
RestoreState bool `yaml:"restore_state"`
} `yaml:"core"`
Advanced struct {
LogLevel string `yaml:"log_level"`
LogPretty bool `yaml:"log_pretty"`
LogJson bool `yaml:"log_json"`
LdapSyncInterval time.Duration `yaml:"ldap_sync_interval"`
StartListenPort int `yaml:"start_listen_port"`
StartCidrV4 string `yaml:"start_cidr_v4"`
StartCidrV6 string `yaml:"start_cidr_v6"`
UseIpV6 bool `yaml:"use_ip_v6"`
ConfigStoragePath string `yaml:"config_storage_path"` // keep empty to disable config export to file
ExpiryCheckInterval time.Duration `yaml:"expiry_check_interval"`
RulePrioOffset int `yaml:"rule_prio_offset"`
RouteTableOffset int `yaml:"route_table_offset"`
} `yaml:"advanced"`
Statistics struct {
UsePingChecks bool `yaml:"use_ping_checks"`
PingCheckWorkers int `yaml:"ping_check_workers"`
PingUnprivileged bool `yaml:"ping_unprivileged"`
PingCheckInterval time.Duration `yaml:"ping_check_interval"`
DataCollectionInterval time.Duration `yaml:"data_collection_interval"`
CollectInterfaceData bool `yaml:"collect_interface_data"`
CollectPeerData bool `yaml:"collect_peer_data"`
CollectAuditData bool `yaml:"collect_audit_data"`
} `yaml:"statistics"`
Mail MailConfig `yaml:"mail"`
Auth Auth `yaml:"auth"`
Database DatabaseConfig `yaml:"database"`
Web WebConfig `yaml:"web"`
}
func (c *Config) LogStartupValues() {
logrus.Debug("WireGuard Portal Features:")
logrus.Debugf(" - EditableKeys: %t", c.Core.EditableKeys)
logrus.Debugf(" - CreateDefaultPeer: %t", c.Core.CreateDefaultPeer)
logrus.Debugf(" - SelfProvisioningAllowed: %t", c.Core.SelfProvisioningAllowed)
logrus.Debugf(" - ImportExisting: %t", c.Core.ImportExisting)
logrus.Debugf(" - RestoreState: %t", c.Core.RestoreState)
logrus.Debugf(" - UseIpV6: %t", c.Advanced.UseIpV6)
logrus.Debugf(" - CollectInterfaceData: %t", c.Statistics.CollectInterfaceData)
logrus.Debugf(" - CollectPeerData: %t", c.Statistics.CollectPeerData)
logrus.Debugf(" - CollectAuditData: %t", c.Statistics.CollectAuditData)
logrus.Debug("WireGuard Portal Settings:")
logrus.Debugf(" - ConfigStoragePath: %s", c.Advanced.ConfigStoragePath)
logrus.Debugf(" - ExternalUrl: %s", c.Web.ExternalUrl)
logrus.Debug("WireGuard Portal Authentication:")
logrus.Debugf(" - OIDC Providers: %d", len(c.Auth.OpenIDConnect))
logrus.Debugf(" - OAuth Providers: %d", len(c.Auth.OAuth))
logrus.Debugf(" - Ldap Providers: %d", len(c.Auth.Ldap))
}
func defaultConfig() *Config {
cfg := &Config{}
cfg.Core.ImportExisting = true
cfg.Core.RestoreState = true
cfg.Database = DatabaseConfig{
Type: "sqlite",
DSN: "sqlite.db",
}
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",
}
cfg.Auth.CallbackUrlPrefix = "/api/v0"
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.Statistics.UsePingChecks = true
cfg.Statistics.PingCheckWorkers = 10
cfg.Statistics.PingUnprivileged = false
cfg.Statistics.PingCheckInterval = 1 * time.Minute
cfg.Statistics.DataCollectionInterval = 10 * time.Second
cfg.Statistics.CollectInterfaceData = true
cfg.Statistics.CollectPeerData = true
cfg.Statistics.CollectAuditData = true
cfg.Mail = MailConfig{
Host: "127.0.0.1",
Port: 25,
Encryption: MailEncryptionNone,
CertValidation: false,
Username: "",
Password: "",
AuthType: MailAuthPlain,
From: "Wireguard Portal <noreply@wireguard.local>",
LinkOnly: false,
}
return cfg
}
func GetConfig() (*Config, error) {
cfg := defaultConfig()
// override config values from YAML file
cfgFileName := "config.yml"
if envCfgFileName := os.Getenv("WG_PORTAL_CONFIG"); envCfgFileName != "" {
cfgFileName = envCfgFileName
}
if err := loadConfigFile(cfg, cfgFileName); err != nil {
return nil, fmt.Errorf("failed to load config from yaml: %w", err)
}
return cfg, nil
}
func loadConfigFile(cfg any, filename string) error {
f, err := os.Open(filename)
if err != nil {
return err
}
defer func(f *os.File) {
if err := f.Close(); err != nil {
logrus.Errorf("failed to close configuration file %s: %v", filename, err)
}
}(f)
decoder := yaml.NewDecoder(f)
err = decoder.Decode(cfg)
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,19 @@
package config
import "time"
type SupportedDatabase string
const (
DatabaseMySQL SupportedDatabase = "mysql"
DatabaseMsSQL SupportedDatabase = "mssql"
DatabasePostgres SupportedDatabase = "postgres"
DatabaseSQLite SupportedDatabase = "sqlite"
)
type DatabaseConfig struct {
Debug bool `yaml:"debug"`
SlowQueryThreshold time.Duration `yaml:"slow_query_threshold"` // 0 means no logging of slow queries
Type SupportedDatabase `yaml:"type"`
DSN string `yaml:"dsn"` // On SQLite: the database file-path, otherwise the dsn (see: https://gorm.io/docs/connecting_to_the_database.html)
}

30
internal/config/mail.go Normal file
View File

@@ -0,0 +1,30 @@
package config
type MailEncryption string
const (
MailEncryptionNone MailEncryption = "none"
MailEncryptionTLS MailEncryption = "tls"
MailEncryptionStartTLS MailEncryption = "starttls"
)
type MailAuthType string
const (
MailAuthPlain MailAuthType = "plain"
MailAuthLogin MailAuthType = "login"
MailAuthCramMD5 MailAuthType = "crammd5"
)
type MailConfig struct {
Host string `yaml:"host"`
Port int `yaml:"port"`
Encryption MailEncryption `yaml:"encryption"`
CertValidation bool `yaml:"cert_validation"`
Username string `yaml:"username"`
Password string `yaml:"password"`
AuthType MailAuthType `yaml:"auth_type"`
From string `yaml:"from"`
LinkOnly bool `yaml:"link_only"`
}

12
internal/config/web.go Normal file
View File

@@ -0,0 +1,12 @@
package config
type WebConfig struct {
RequestLogging bool `yaml:"request_logging"`
ExternalUrl string `yaml:"external_url"`
ListeningAddress string `yaml:"listening_address"`
SessionIdentifier string `yaml:"session_identifier"`
SessionSecret string `yaml:"session_secret"`
CsrfSecret string `yaml:"csrf_secret"`
SiteTitle string `yaml:"site_title"`
SiteCompanyName string `yaml:"site_company_name"`
}