WIP: support for multiple WireGuard devices (#2)

This commit is contained in:
Christoph Haas
2021-03-21 12:36:11 +01:00
parent 5f4c041ee7
commit 6ab00ef567
22 changed files with 538 additions and 519 deletions

View File

@@ -1,132 +0,0 @@
package common
import (
"os"
"reflect"
"runtime"
"github.com/h44z/wg-portal/internal/ldap"
"github.com/h44z/wg-portal/internal/users"
"github.com/h44z/wg-portal/internal/wireguard"
"github.com/kelseyhightower/envconfig"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"gopkg.in/yaml.v3"
)
var ErrInvalidSpecification = errors.New("specification must be a struct pointer")
// loadConfigFile parses yaml files. It uses yaml annotation to store the data in a struct.
func loadConfigFile(cfg interface{}, filename string) error {
s := reflect.ValueOf(cfg)
if s.Kind() != reflect.Ptr {
return ErrInvalidSpecification
}
s = s.Elem()
if s.Kind() != reflect.Struct {
return ErrInvalidSpecification
}
f, err := os.Open(filename)
if err != nil {
return errors.Wrapf(err, "failed to open config file %s", filename)
}
defer f.Close()
decoder := yaml.NewDecoder(f)
err = decoder.Decode(cfg)
if err != nil {
return errors.Wrapf(err, "failed to decode config file %s", filename)
}
return nil
}
// loadConfigEnv processes envconfig annotations and loads environment variables to the given configuration struct.
func loadConfigEnv(cfg interface{}) error {
err := envconfig.Process("", cfg)
if err != nil {
return errors.Wrap(err, "failed to process environment config")
}
return nil
}
type Config struct {
Core struct {
ListeningAddress string `yaml:"listeningAddress" envconfig:"LISTENING_ADDRESS"`
ExternalUrl string `yaml:"externalUrl" envconfig:"EXTERNAL_URL"`
Title string `yaml:"title" envconfig:"WEBSITE_TITLE"`
CompanyName string `yaml:"company" envconfig:"COMPANY_NAME"`
MailFrom string `yaml:"mailFrom" envconfig:"MAIL_FROM"`
AdminUser string `yaml:"adminUser" envconfig:"ADMIN_USER"` // must be an email address
AdminPassword string `yaml:"adminPass" envconfig:"ADMIN_PASS"`
EditableKeys bool `yaml:"editableKeys" envconfig:"EDITABLE_KEYS"`
CreateDefaultPeer bool `yaml:"createDefaultPeer" envconfig:"CREATE_DEFAULT_PEER"`
LdapEnabled bool `yaml:"ldapEnabled" envconfig:"LDAP_ENABLED"`
} `yaml:"core"`
Database users.Config `yaml:"database"`
Email MailConfig `yaml:"email"`
LDAP ldap.Config `yaml:"ldap"`
WG wireguard.Config `yaml:"wg"`
}
func NewConfig() *Config {
cfg := &Config{}
// Default config
cfg.Core.ListeningAddress = ":8123"
cfg.Core.Title = "WireGuard VPN"
cfg.Core.CompanyName = "WireGuard Portal"
cfg.Core.ExternalUrl = "http://localhost:8123"
cfg.Core.MailFrom = "WireGuard VPN <noreply@company.com>"
cfg.Core.AdminUser = "admin@wgportal.local"
cfg.Core.AdminPassword = "wgportal"
cfg.Core.LdapEnabled = false
cfg.Database.Typ = "sqlite"
cfg.Database.Database = "data/wg_portal.db"
cfg.LDAP.URL = "ldap://srv-ad01.company.local:389"
cfg.LDAP.BaseDN = "DC=COMPANY,DC=LOCAL"
cfg.LDAP.StartTLS = true
cfg.LDAP.BindUser = "company\\\\ldap_wireguard"
cfg.LDAP.BindPass = "SuperSecret"
cfg.LDAP.Type = "AD"
cfg.LDAP.UserClass = "organizationalPerson"
cfg.LDAP.EmailAttribute = "mail"
cfg.LDAP.FirstNameAttribute = "givenName"
cfg.LDAP.LastNameAttribute = "sn"
cfg.LDAP.PhoneAttribute = "telephoneNumber"
cfg.LDAP.GroupMemberAttribute = "memberOf"
cfg.LDAP.DisabledAttribute = "userAccountControl"
cfg.LDAP.AdminLdapGroup = "CN=WireGuardAdmins,OU=_O_IT,DC=COMPANY,DC=LOCAL"
cfg.WG.DeviceName = "wg0"
cfg.WG.WireGuardConfig = "/etc/wireguard/wg0.conf"
cfg.WG.ManageIPAddresses = true
cfg.Email.Host = "127.0.0.1"
cfg.Email.Port = 25
// Load config from file and environment
cfgFile, ok := os.LookupEnv("CONFIG_FILE")
if !ok {
cfgFile = "config.yml" // Default config file
}
err := loadConfigFile(cfg, cfgFile)
if err != nil {
logrus.Warnf("unable to load config.yml file: %v, using default configuration...", err)
}
err = loadConfigEnv(cfg)
if err != nil {
logrus.Warnf("unable to load environment config: %v", err)
}
if cfg.WG.ManageIPAddresses && runtime.GOOS != "linux" {
logrus.Warnf("managing IP addresses only works on linux, feature disabled...")
cfg.WG.ManageIPAddresses = false
}
return cfg
}

76
internal/common/db.go Normal file
View File

@@ -0,0 +1,76 @@
package common
import (
"fmt"
"os"
"path/filepath"
"time"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"gorm.io/driver/mysql"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
type SupportedDatabase string
const (
SupportedDatabaseMySQL SupportedDatabase = "mysql"
SupportedDatabaseSQLite SupportedDatabase = "sqlite"
)
type DatabaseConfig struct {
Typ SupportedDatabase `yaml:"typ" envconfig:"DATABASE_TYPE"` //mysql or sqlite
Host string `yaml:"host" envconfig:"DATABASE_HOST"`
Port int `yaml:"port" envconfig:"DATABASE_PORT"`
Database string `yaml:"database" envconfig:"DATABASE_NAME"` // On SQLite: the database file-path, otherwise the database name
User string `yaml:"user" envconfig:"DATABASE_USERNAME"`
Password string `yaml:"password" envconfig:"DATABASE_PASSWORD"`
}
func GetDatabaseForConfig(cfg *DatabaseConfig) (db *gorm.DB, err error) {
switch cfg.Typ {
case SupportedDatabaseSQLite:
if _, err = os.Stat(filepath.Dir(cfg.Database)); os.IsNotExist(err) {
if err = os.MkdirAll(filepath.Dir(cfg.Database), 0700); err != nil {
return
}
}
db, err = gorm.Open(sqlite.Open(cfg.Database), &gorm.Config{})
if err != nil {
return
}
case SupportedDatabaseMySQL:
connectionString := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local", cfg.User, cfg.Password, cfg.Host, cfg.Port, cfg.Database)
db, err = gorm.Open(mysql.Open(connectionString), &gorm.Config{})
if err != nil {
return
}
sqlDB, _ := db.DB()
sqlDB.SetConnMaxLifetime(time.Minute * 5)
sqlDB.SetMaxIdleConns(2)
sqlDB.SetMaxOpenConns(10)
err = sqlDB.Ping() // This DOES open a connection if necessary. This makes sure the database is accessible
if err != nil {
return nil, errors.Wrap(err, "failed to ping mysql authentication database")
}
}
// Enable Logger (logrus)
logCfg := logger.Config{
SlowThreshold: time.Second, // all slower than one second
Colorful: false,
LogLevel: logger.Silent, // default: log nothing
}
if logrus.StandardLogger().GetLevel() == logrus.TraceLevel {
logCfg.LogLevel = logger.Info
logCfg.SlowThreshold = 500 * time.Millisecond // all slower than half a second
}
db.Config.Logger = logger.New(logrus.StandardLogger(), logCfg)
return
}

View File

@@ -60,6 +60,16 @@ func ListToString(lst []string) string {
return strings.Join(lst, ", ")
}
// ListContains checks if a needle exists in the given list.
func ListContains(lst []string, needle string) bool {
for _, entry := range lst {
if entry == needle {
return true
}
}
return false
}
// https://yourbasic.org/golang/formatting-byte-size-to-human-readable-format/
func ByteCountSI(b int64) string {
const unit = 1000