From 3024d36d761e680dc6d65ca9f7bf785fca7b5347 Mon Sep 17 00:00:00 2001 From: Cameron <113076756+cameronaw13@users.noreply.github.com> Date: Mon, 25 Dec 2023 10:58:31 -0800 Subject: [PATCH 01/16] env variable file support (#391) --- README.md | 59 +++++++++++++++++++++++------------------- main.go | 38 ++++++++++++++++++++++----- store/jsondb/jsondb.go | 17 ++++++++---- util/config.go | 4 ++- util/util.go | 20 ++++++++++++-- 5 files changed, 97 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index 0da4652..f7c41f8 100644 --- a/README.md +++ b/README.md @@ -36,33 +36,38 @@ docker-compose up ## Environment Variables -| Variable | Description | Default | -|-----------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------| -| `BASE_PATH` | Set this variable if you run wireguard-ui under a subpath of your reverse proxy virtual host (e.g. /wireguard)) | N/A | -| `BIND_ADDRESS` | The addresses that can access to the web interface and the port | 0.0.0.0:80 | -| `SESSION_SECRET` | The secret key used to encrypt the session cookies. Set this to a random value | N/A | -| `WGUI_USERNAME` | The username for the login page. Used for db initialization only | `admin` | -| `WGUI_PASSWORD` | The password for the user on the login page. Will be hashed automatically. Used for db initialization only | `admin` | -| `WGUI_PASSWORD_HASH` | The password hash for the user on the login page. (alternative to `WGUI_PASSWORD`). Used for db initialization only | N/A | -| `WGUI_ENDPOINT_ADDRESS` | The default endpoint address used in global settings where clients should connect to | Resolved to your public ip address | -| `WGUI_FAVICON_FILE_PATH` | The file path used as website favicon | Embedded WireGuard logo | -| `WGUI_DNS` | The default DNS servers (comma-separated-list) used in the global settings | `1.1.1.1` | -| `WGUI_MTU` | The default MTU used in global settings | `1450` | -| `WGUI_PERSISTENT_KEEPALIVE` | The default persistent keepalive for WireGuard in global settings | `15` | -| `WGUI_FIREWALL_MARK` | The default WireGuard firewall mark | `0xca6c` (51820) | -| `WGUI_TABLE` | The default WireGuard table value settings | `auto` | -| `WGUI_CONFIG_FILE_PATH` | The default WireGuard config file path used in global settings | `/etc/wireguard/wg0.conf` | -| `WGUI_LOG_LEVEL` | The default log level. Possible values: `DEBUG`, `INFO`, `WARN`, `ERROR`, `OFF` | `INFO` | -| `WG_CONF_TEMPLATE` | The custom `wg.conf` config file template. Please refer to our [default template](https://github.com/ngoduykhanh/wireguard-ui/blob/master/templates/wg.conf) | N/A | -| `EMAIL_FROM_ADDRESS` | The sender email address | N/A | -| `EMAIL_FROM_NAME` | The sender name | `WireGuard UI` | -| `SENDGRID_API_KEY` | The SendGrid api key | N/A | -| `SMTP_HOSTNAME` | The SMTP IP address or hostname | `127.0.0.1` | -| `SMTP_PORT` | The SMTP port | `25` | -| `SMTP_USERNAME` | The SMTP username | N/A | -| `SMTP_PASSWORD` | The SMTP user password | N/A | -| `SMTP_AUTH_TYPE` | The SMTP authentication type. Possible values: `PLAIN`, `LOGIN`, `NONE` | `NONE` | -| `SMTP_ENCRYPTION` | the encryption method. Possible values: `NONE`, `SSL`, `SSLTLS`, `TLS`, `STARTTLS` | `STARTTLS` | +| Variable | Description | Default | +|-----------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------| +| `BASE_PATH` | Set this variable if you run wireguard-ui under a subpath of your reverse proxy virtual host (e.g. /wireguard) | N/A | +| `BIND_ADDRESS` | The addresses that can access to the web interface and the port | 0.0.0.0:80 | +| `SESSION_SECRET` | The secret key used to encrypt the session cookies. Set this to a random value | N/A | +| `SESSION_SECRET_FILE` | Optional filepath for the secret key used to encrypt the session cookies. Leave `SESSION_SECRET` blank to take effect | N/A | +| `WGUI_USERNAME` | The username for the login page. Used for db initialization only | `admin` | +| `WGUI_PASSWORD` | The password for the user on the login page. Will be hashed automatically. Used for db initialization only | `admin` | +| `WGUI_PASSWORD_FILE` | Optional filepath for the user login password. Will be hashed automatically. Used for db initialization only. Leave `WGUI_PASSWORD` blank to take effect | N/A | +| `WGUI_PASSWORD_HASH` | The password hash for the user on the login page. (alternative to `WGUI_PASSWORD`). Used for db initialization only | N/A | +| `WGUI_PASSWORD_HASH_FILE` | Optional filepath for the user login password hash. (alternative to `WGUI_PASSWORD_FILE`). Used for db initialization only. Leave `WGUI_PASSWORD_HASH` blank to take effect | N/A | +| `WGUI_ENDPOINT_ADDRESS` | The default endpoint address used in global settings where clients should connect to | Resolved to your public ip address | +| `WGUI_FAVICON_FILE_PATH` | The file path used as website favicon | Embedded WireGuard logo | +| `WGUI_DNS` | The default DNS servers (comma-separated-list) used in the global settings | `1.1.1.1` | +| `WGUI_MTU` | The default MTU used in global settings | `1450` | +| `WGUI_PERSISTENT_KEEPALIVE` | The default persistent keepalive for WireGuard in global settings | `15` | +| `WGUI_FIREWALL_MARK` | The default WireGuard firewall mark | `0xca6c` (51820) | +| `WGUI_TABLE` | The default WireGuard table value settings | `auto` | +| `WGUI_CONFIG_FILE_PATH` | The default WireGuard config file path used in global settings | `/etc/wireguard/wg0.conf` | +| `WGUI_LOG_LEVEL` | The default log level. Possible values: `DEBUG`, `INFO`, `WARN`, `ERROR`, `OFF` | `INFO` | +| `WG_CONF_TEMPLATE` | The custom `wg.conf` config file template. Please refer to our [default template](https://github.com/ngoduykhanh/wireguard-ui/blob/master/templates/wg.conf) | N/A | +| `EMAIL_FROM_ADDRESS` | The sender email address | N/A | +| `EMAIL_FROM_NAME` | The sender name | `WireGuard UI` | +| `SENDGRID_API_KEY` | The SendGrid api key | N/A | +| `SENDGRID_API_KEY_FILE` | Optional filepath for the SendGrid api key. Leave `SENDGRID_API_KEY` blank to take effect | N/A | +| `SMTP_HOSTNAME` | The SMTP IP address or hostname | `127.0.0.1` | +| `SMTP_PORT` | The SMTP port | `25` | +| `SMTP_USERNAME` | The SMTP username | N/A | +| `SMTP_PASSWORD` | The SMTP user password | N/A | +| `SMTP_PASSWORD_FILE` | Optional filepath for the SMTP user password. Leave `SMTP_PASSWORD` blank to take effect | N/A | +| `SMTP_AUTH_TYPE` | The SMTP authentication type. Possible values: `PLAIN`, `LOGIN`, `NONE` | `NONE` | +| `SMTP_ENCRYPTION` | The encryption method. Possible values: `NONE`, `SSL`, `SSLTLS`, `TLS`, `STARTTLS` | `STARTTLS` | ### Defaults for server configuration diff --git a/main.go b/main.go index 0131208..60c5c35 100644 --- a/main.go +++ b/main.go @@ -4,14 +4,15 @@ import ( "embed" "flag" "fmt" - "github.com/labstack/echo/v4" - "github.com/labstack/gommon/log" - "github.com/ngoduykhanh/wireguard-ui/store" "io/fs" "net/http" "os" "time" + "github.com/labstack/echo/v4" + "github.com/labstack/gommon/log" + "github.com/ngoduykhanh/wireguard-ui/store" + "github.com/ngoduykhanh/wireguard-ui/emailer" "github.com/ngoduykhanh/wireguard-ui/handler" "github.com/ngoduykhanh/wireguard-ui/router" @@ -70,16 +71,41 @@ func init() { flag.StringVar(&flagSmtpHostname, "smtp-hostname", util.LookupEnvOrString("SMTP_HOSTNAME", flagSmtpHostname), "SMTP Hostname") flag.IntVar(&flagSmtpPort, "smtp-port", util.LookupEnvOrInt("SMTP_PORT", flagSmtpPort), "SMTP Port") flag.StringVar(&flagSmtpUsername, "smtp-username", util.LookupEnvOrString("SMTP_USERNAME", flagSmtpUsername), "SMTP Username") - flag.StringVar(&flagSmtpPassword, "smtp-password", util.LookupEnvOrString("SMTP_PASSWORD", flagSmtpPassword), "SMTP Password") flag.BoolVar(&flagSmtpNoTLSCheck, "smtp-no-tls-check", util.LookupEnvOrBool("SMTP_NO_TLS_CHECK", flagSmtpNoTLSCheck), "Disable TLS verification for SMTP. This is potentially dangerous.") flag.StringVar(&flagSmtpEncryption, "smtp-encryption", util.LookupEnvOrString("SMTP_ENCRYPTION", flagSmtpEncryption), "SMTP Encryption : NONE, SSL, SSLTLS, TLS or STARTTLS (by default)") flag.StringVar(&flagSmtpAuthType, "smtp-auth-type", util.LookupEnvOrString("SMTP_AUTH_TYPE", flagSmtpAuthType), "SMTP Auth Type : PLAIN, LOGIN or NONE.") - flag.StringVar(&flagSendgridApiKey, "sendgrid-api-key", util.LookupEnvOrString("SENDGRID_API_KEY", flagSendgridApiKey), "Your sendgrid api key.") flag.StringVar(&flagEmailFrom, "email-from", util.LookupEnvOrString("EMAIL_FROM_ADDRESS", flagEmailFrom), "'From' email address.") flag.StringVar(&flagEmailFromName, "email-from-name", util.LookupEnvOrString("EMAIL_FROM_NAME", flagEmailFromName), "'From' email name.") - flag.StringVar(&flagSessionSecret, "session-secret", util.LookupEnvOrString("SESSION_SECRET", flagSessionSecret), "The key used to encrypt session cookies.") flag.StringVar(&flagWgConfTemplate, "wg-conf-template", util.LookupEnvOrString("WG_CONF_TEMPLATE", flagWgConfTemplate), "Path to custom wg.conf template.") flag.StringVar(&flagBasePath, "base-path", util.LookupEnvOrString("BASE_PATH", flagBasePath), "The base path of the URL") + + var ( + smtpPasswordLookup = util.LookupEnvOrString("SMTP_PASSWORD", flagSmtpPassword) + sengridApiKeyLookup = util.LookupEnvOrString("SENDGRID_API_KEY", flagSendgridApiKey) + sessionSecretLookup = util.LookupEnvOrString("SESSION_SECRET", flagSessionSecret) + ) + + // check empty smtpPassword env var + if smtpPasswordLookup != "" { + flag.StringVar(&flagSmtpPassword, "smtp-password", smtpPasswordLookup, "SMTP Password") + } else { + flag.StringVar(&flagSmtpPassword, "smtp-password", util.LookupEnvOrFile("SMTP_PASSWORD_FILE", flagSmtpPassword), "SMTP Password File") + } + + // check empty sengridApiKey env var + if sengridApiKeyLookup != "" { + flag.StringVar(&flagSendgridApiKey, "sendgrid-api-key", sengridApiKeyLookup, "Your sendgrid api key.") + } else { + flag.StringVar(&flagSendgridApiKey, "sendgrid-api-key", util.LookupEnvOrFile("SENDGRID_API_KEY_FILE", flagSendgridApiKey), "File containing your sendgrid api key.") + } + + // check empty sessionSecret env var + if sessionSecretLookup != "" { + flag.StringVar(&flagSessionSecret, "session-secret", sessionSecretLookup, "The key used to encrypt session cookies.") + } else { + flag.StringVar(&flagSessionSecret, "session-secret", util.LookupEnvOrFile("SESSION_SECRET_FILE", flagSessionSecret), "File containing the key used to encrypt session cookies.") + } + flag.Parse() // update runtime config diff --git a/store/jsondb/jsondb.go b/store/jsondb/jsondb.go index 0b674b0..4ccd9b7 100644 --- a/store/jsondb/jsondb.go +++ b/store/jsondb/jsondb.go @@ -128,13 +128,20 @@ func (o *JsonDB) Init() error { user.Admin = util.DefaultIsAdmin user.PasswordHash = util.LookupEnvOrString(util.PasswordHashEnvVar, "") if user.PasswordHash == "" { - plaintext := util.LookupEnvOrString(util.PasswordEnvVar, util.DefaultPassword) - hash, err := util.HashPassword(plaintext) - if err != nil { - return err + user.PasswordHash = util.LookupEnvOrFile(util.PasswordHashFileEnvVar, "") + if user.PasswordHash == "" { + plaintext := util.LookupEnvOrString(util.PasswordEnvVar, util.DefaultPassword) + if plaintext == util.DefaultPassword { + plaintext = util.LookupEnvOrFile(util.PasswordFileEnvVar, util.DefaultPassword) + } + hash, err := util.HashPassword(plaintext) + if err != nil { + return err + } + user.PasswordHash = hash } - user.PasswordHash = hash } + o.conn.Write("users", user.Username, user) os.Chmod(path.Join(path.Join(o.dbPath, "users"), user.Username+".json"), 0600) } diff --git a/util/config.go b/util/config.go index 3c3bd18..6d5a8df 100644 --- a/util/config.go +++ b/util/config.go @@ -30,12 +30,14 @@ const ( DefaultDNS = "1.1.1.1" DefaultMTU = 1450 DefaultPersistentKeepalive = 15 - DefaultFirewallMark = "0xca6c" // i.e. 51820 + DefaultFirewallMark = "0xca6c" // i.e. 51820 DefaultTable = "auto" DefaultConfigFilePath = "/etc/wireguard/wg0.conf" UsernameEnvVar = "WGUI_USERNAME" PasswordEnvVar = "WGUI_PASSWORD" + PasswordFileEnvVar = "WGUI_PASSWORD_FILE" PasswordHashEnvVar = "WGUI_PASSWORD_HASH" + PasswordHashFileEnvVar = "WGUI_PASSWORD_HASH_FILE" FaviconFilePathEnvVar = "WGUI_FAVICON_FILE_PATH" EndpointAddressEnvVar = "WGUI_ENDPOINT_ADDRESS" DNSEnvVar = "WGUI_DNS" diff --git a/util/util.go b/util/util.go index 44f8f01..1203082 100644 --- a/util/util.go +++ b/util/util.go @@ -1,11 +1,10 @@ package util import ( + "bufio" "encoding/json" "errors" "fmt" - "github.com/ngoduykhanh/wireguard-ui/store" - "golang.org/x/mod/sumdb/dirhash" "io" "io/fs" "io/ioutil" @@ -19,6 +18,9 @@ import ( "text/template" "time" + "github.com/ngoduykhanh/wireguard-ui/store" + "golang.org/x/mod/sumdb/dirhash" + externalip "github.com/glendc/go-external-ip" "github.com/labstack/gommon/log" "github.com/ngoduykhanh/wireguard-ui/model" @@ -466,6 +468,20 @@ func LookupEnvOrStrings(key string, defaultVal []string) []string { return defaultVal } +func LookupEnvOrFile(key string, defaultVal string) string { + if val, ok := os.LookupEnv(key); ok { + if file, err := os.Open(val); err == nil { + var content string + scanner := bufio.NewScanner(file) + for scanner.Scan() { + content += scanner.Text() + } + return content + } + } + return defaultVal +} + func StringFromEmbedFile(embed fs.FS, filename string) (string, error) { file, err := embed.Open(filename) if err != nil { From a06bce88e0245caab4eac4894f0c035949cbc1d8 Mon Sep 17 00:00:00 2001 From: Marcus Wichelmann Date: Mon, 25 Dec 2023 20:03:29 +0100 Subject: [PATCH 02/16] fix: add content-type check to user management routes to mitigate CSRF (#427) --- main.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/main.go b/main.go index 60c5c35..b226ccc 100644 --- a/main.go +++ b/main.go @@ -172,15 +172,19 @@ func main() { app.GET(util.BasePath, handler.WireGuardClients(db), handler.ValidSession) + // Important: Make sure that all non-GET routes check the request content type using handler.ContentTypeJson to + // mitigate CSRF attacks. This is effective, because browsers don't allow setting the Content-Type header on + // cross-origin requests. + if !util.DisableLogin { app.GET(util.BasePath+"/login", handler.LoginPage()) - app.POST(util.BasePath+"/login", handler.Login(db)) + app.POST(util.BasePath+"/login", handler.Login(db), handler.ContentTypeJson) app.GET(util.BasePath+"/logout", handler.Logout(), handler.ValidSession) app.GET(util.BasePath+"/profile", handler.LoadProfile(db), handler.ValidSession) app.GET(util.BasePath+"/users-settings", handler.UsersSettings(db), handler.ValidSession, handler.NeedsAdmin) - app.POST(util.BasePath+"/update-user", handler.UpdateUser(db), handler.ValidSession) - app.POST(util.BasePath+"/create-user", handler.CreateUser(db), handler.ValidSession, handler.NeedsAdmin) - app.POST(util.BasePath+"/remove-user", handler.RemoveUser(db), handler.ValidSession, handler.NeedsAdmin) + app.POST(util.BasePath+"/update-user", handler.UpdateUser(db), handler.ValidSession, handler.ContentTypeJson) + app.POST(util.BasePath+"/create-user", handler.CreateUser(db), handler.ValidSession, handler.ContentTypeJson, handler.NeedsAdmin) + app.POST(util.BasePath+"/remove-user", handler.RemoveUser(db), handler.ValidSession, handler.ContentTypeJson, handler.NeedsAdmin) app.GET(util.BasePath+"/getusers", handler.GetUsers(db), handler.ValidSession, handler.NeedsAdmin) app.GET(util.BasePath+"/api/user/:username", handler.GetUser(db), handler.ValidSession) } From 13a4c05ff5f28523ea0d827f4c8085393e65c1ab Mon Sep 17 00:00:00 2001 From: Marcus Wichelmann Date: Mon, 25 Dec 2023 20:07:47 +0100 Subject: [PATCH 03/16] fix: add basic server-side input validation (#435) This mitigates possible path traversal attacks by using e.g. "../user" as a user name. --- handler/routes.go | 49 ++++++++++++++++++++++++++++++++++++--- model/wake_on_lan_host.go | 9 ++++++- store/jsondb/jsondb.go | 14 ++++------- 3 files changed, 58 insertions(+), 14 deletions(-) diff --git a/handler/routes.go b/handler/routes.go index a2ceeaf..c0413d4 100644 --- a/handler/routes.go +++ b/handler/routes.go @@ -8,6 +8,7 @@ import ( "io/fs" "net/http" "os" + "regexp" "sort" "strings" "time" @@ -26,6 +27,8 @@ import ( "github.com/ngoduykhanh/wireguard-ui/util" ) +var usernameRegexp = regexp.MustCompile("^\\w[\\w\\-.]*$") + // Health check handler func Health() echo.HandlerFunc { return func(c echo.Context) error { @@ -63,6 +66,10 @@ func Login(db store.IStore) echo.HandlerFunc { password := data["password"].(string) rememberMe := data["rememberMe"].(bool) + if !usernameRegexp.MatchString(username) { + return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Please provide a valid username"}) + } + dbuser, err := db.GetUserByName(username) if err != nil { return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot query user from DB"}) @@ -135,9 +142,12 @@ func GetUsers(db store.IStore) echo.HandlerFunc { // GetUser handler returns a JSON object of single user func GetUser(db store.IStore) echo.HandlerFunc { return func(c echo.Context) error { - username := c.Param("username") + if !usernameRegexp.MatchString(username) { + return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Please provide a valid username"}) + } + if !isAdmin(c) && (username != currentUser(c)) { return c.JSON(http.StatusForbidden, jsonHTTPResponse{false, "Manager cannot access other user data"}) } @@ -200,12 +210,16 @@ func UpdateUser(db store.IStore) echo.HandlerFunc { admin = false } + if !usernameRegexp.MatchString(previousUsername) { + return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Please provide a valid username"}) + } + user, err := db.GetUserByName(previousUsername) if err != nil { return c.JSON(http.StatusNotFound, jsonHTTPResponse{false, err.Error()}) } - if username == "" { + if username == "" || !usernameRegexp.MatchString(username) { return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Please provide a valid username"}) } else { user.Username = username @@ -261,7 +275,7 @@ func CreateUser(db store.IStore) echo.HandlerFunc { password := data["password"].(string) admin := data["admin"].(bool) - if username == "" { + if username == "" || !usernameRegexp.MatchString(username) { return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Please provide a valid username"}) } else { user.Username = username @@ -303,6 +317,10 @@ func RemoveUser(db store.IStore) echo.HandlerFunc { username := data["username"].(string) + if !usernameRegexp.MatchString(username) { + return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Please provide a valid username"}) + } + if username == currentUser(c) { return c.JSON(http.StatusForbidden, jsonHTTPResponse{false, "User cannot delete itself"}) } @@ -357,6 +375,11 @@ func GetClient(db store.IStore) echo.HandlerFunc { return func(c echo.Context) error { clientID := c.Param("id") + + if _, err := xid.FromString(clientID); err != nil { + return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Please provide a valid client ID"}) + } + qrCodeSettings := model.QRCodeSettings{ Enabled: true, IncludeDNS: true, @@ -485,6 +508,10 @@ func EmailClient(db store.IStore, mailer emailer.Emailer, emailSubject, emailCon c.Bind(&payload) // TODO validate email + if _, err := xid.FromString(payload.ID); err != nil { + return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Please provide a valid client ID"}) + } + qrCodeSettings := model.QRCodeSettings{ Enabled: true, IncludeDNS: true, @@ -536,6 +563,10 @@ func UpdateClient(db store.IStore) echo.HandlerFunc { var _client model.Client c.Bind(&_client) + if _, err := xid.FromString(_client.ID); err != nil { + return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Please provide a valid client ID"}) + } + // validate client existence clientData, err := db.GetClientByID(_client.ID, model.QRCodeSettings{Enabled: false}) if err != nil { @@ -642,6 +673,10 @@ func SetClientStatus(db store.IStore) echo.HandlerFunc { clientID := data["id"].(string) status := data["status"].(bool) + if _, err := xid.FromString(clientID); err != nil { + return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Please provide a valid client ID"}) + } + clientData, err := db.GetClientByID(clientID, model.QRCodeSettings{Enabled: false}) if err != nil { return c.JSON(http.StatusNotFound, jsonHTTPResponse{false, err.Error()}) @@ -667,6 +702,10 @@ func DownloadClient(db store.IStore) echo.HandlerFunc { return c.JSON(http.StatusNotFound, jsonHTTPResponse{false, "Missing clientid parameter"}) } + if _, err := xid.FromString(clientID); err != nil { + return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Please provide a valid client ID"}) + } + clientData, err := db.GetClientByID(clientID, model.QRCodeSettings{Enabled: false}) if err != nil { log.Errorf("Cannot generate client id %s config file for downloading: %v", clientID, err) @@ -700,6 +739,10 @@ func RemoveClient(db store.IStore) echo.HandlerFunc { client := new(model.Client) c.Bind(client) + if _, err := xid.FromString(client.ID); err != nil { + return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Please provide a valid client ID"}) + } + // delete client from database if err := db.DeleteClient(client.ID); err != nil { diff --git a/model/wake_on_lan_host.go b/model/wake_on_lan_host.go index 2d1ab73..73966f0 100644 --- a/model/wake_on_lan_host.go +++ b/model/wake_on_lan_host.go @@ -2,6 +2,7 @@ package model import ( "errors" + "net" "strings" "time" ) @@ -18,7 +19,13 @@ func (host WakeOnLanHost) ResolveResourceName() (string, error) { return "", errors.New("mac Address is Empty") } resourceName = strings.ToUpper(resourceName) - return strings.ReplaceAll(resourceName, ":", "-"), nil + resourceName = strings.ReplaceAll(resourceName, ":", "-") + + if _, err := net.ParseMAC(resourceName); err != nil { + return "", errors.New("invalid mac address") + } + + return resourceName, nil } const WakeOnLanHostCollectionName = "wake_on_lan_hosts" diff --git a/store/jsondb/jsondb.go b/store/jsondb/jsondb.go index 4ccd9b7..b961e95 100644 --- a/store/jsondb/jsondb.go +++ b/store/jsondb/jsondb.go @@ -38,12 +38,12 @@ func New(dbPath string) (*JsonDB, error) { func (o *JsonDB) Init() error { var clientPath string = path.Join(o.dbPath, "clients") var serverPath string = path.Join(o.dbPath, "server") + var userPath string = path.Join(o.dbPath, "users") var wakeOnLanHostsPath string = path.Join(o.dbPath, "wake_on_lan_hosts") var serverInterfacePath string = path.Join(serverPath, "interfaces.json") var serverKeyPairPath string = path.Join(serverPath, "keypair.json") var globalSettingPath string = path.Join(serverPath, "global_settings.json") var hashesPath string = path.Join(serverPath, "hashes.json") - var userPath string = path.Join(serverPath, "users.json") // create directories if they do not exist if _, err := os.Stat(clientPath); os.IsNotExist(err) { @@ -52,12 +52,12 @@ func (o *JsonDB) Init() error { if _, err := os.Stat(serverPath); os.IsNotExist(err) { os.MkdirAll(serverPath, os.ModePerm) } - if _, err := os.Stat(wakeOnLanHostsPath); os.IsNotExist(err) { - os.MkdirAll(wakeOnLanHostsPath, os.ModePerm) - } if _, err := os.Stat(userPath); os.IsNotExist(err) { os.MkdirAll(userPath, os.ModePerm) } + if _, err := os.Stat(wakeOnLanHostsPath); os.IsNotExist(err) { + os.MkdirAll(wakeOnLanHostsPath, os.ModePerm) + } // server's interface if _, err := os.Stat(serverInterfacePath); os.IsNotExist(err) { @@ -149,12 +149,6 @@ func (o *JsonDB) Init() error { return nil } -// GetUser func to query user info from the database -func (o *JsonDB) GetUser() (model.User, error) { - user := model.User{} - return user, o.conn.Read("server", "users", &user) -} - // GetUsers func to get all users from the database func (o *JsonDB) GetUsers() ([]model.User, error) { var users []model.User From 585b55c2ee7d39e3f5c3104ece97856480c2ef37 Mon Sep 17 00:00:00 2001 From: Cameron <113076756+cameronaw13@users.noreply.github.com> Date: Mon, 25 Dec 2023 11:17:31 -0800 Subject: [PATCH 04/16] fix: handle os.chmod errors (#457) --- store/jsondb/jsondb.go | 55 ++++++++++++++++++++++++------ store/jsondb/jsondb_wake_on_lan.go | 8 +++-- util/util.go | 5 +++ 3 files changed, 55 insertions(+), 13 deletions(-) diff --git a/store/jsondb/jsondb.go b/store/jsondb/jsondb.go index b961e95..757ccdc 100644 --- a/store/jsondb/jsondb.go +++ b/store/jsondb/jsondb.go @@ -68,7 +68,10 @@ func (o *JsonDB) Init() error { serverInterface.PostDown = util.LookupEnvOrString(util.ServerPostDownScriptEnvVar, "") serverInterface.UpdatedAt = time.Now().UTC() o.conn.Write("server", "interfaces", serverInterface) - os.Chmod(serverInterfacePath, 0600) + err := util.ManagePerms(serverInterfacePath) + if err != nil { + return err + } } // server's key pair @@ -83,7 +86,10 @@ func (o *JsonDB) Init() error { serverKeyPair.PublicKey = key.PublicKey().String() serverKeyPair.UpdatedAt = time.Now().UTC() o.conn.Write("server", "keypair", serverKeyPair) - os.Chmod(serverKeyPairPath, 0600) + err = util.ManagePerms(serverKeyPairPath) + if err != nil { + return err + } } // global settings @@ -108,7 +114,10 @@ func (o *JsonDB) Init() error { globalSetting.ConfigFilePath = util.LookupEnvOrString(util.ConfigFilePathEnvVar, util.DefaultConfigFilePath) globalSetting.UpdatedAt = time.Now().UTC() o.conn.Write("server", "global_settings", globalSetting) - os.Chmod(globalSettingPath, 0600) + err := util.ManagePerms(globalSettingPath) + if err != nil { + return err + } } // hashes @@ -117,7 +126,10 @@ func (o *JsonDB) Init() error { clientServerHashes.Client = "none" clientServerHashes.Server = "none" o.conn.Write("server", "hashes", clientServerHashes) - os.Chmod(hashesPath, 0600) + err := util.ManagePerms(hashesPath) + if err != nil { + return err + } } // user info @@ -143,7 +155,10 @@ func (o *JsonDB) Init() error { } o.conn.Write("users", user.Username, user) - os.Chmod(path.Join(path.Join(o.dbPath, "users"), user.Username+".json"), 0600) + err = util.ManagePerms(path.Join(path.Join(o.dbPath, "users"), user.Username+".json")) + if err != nil { + return err + } } return nil @@ -183,7 +198,10 @@ func (o *JsonDB) GetUserByName(username string) (model.User, error) { func (o *JsonDB) SaveUser(user model.User) error { userPath := path.Join(path.Join(o.dbPath, "users"), user.Username+".json") output := o.conn.Write("users", user.Username, user) - os.Chmod(userPath, 0600) + err := util.ManagePerms(userPath) + if err != nil { + return err + } return output } @@ -296,7 +314,10 @@ func (o *JsonDB) GetClientByID(clientID string, qrCodeSettings model.QRCodeSetti func (o *JsonDB) SaveClient(client model.Client) error { clientPath := path.Join(path.Join(o.dbPath, "clients"), client.ID+".json") output := o.conn.Write("clients", client.ID, client) - os.Chmod(clientPath, 0600) + err := util.ManagePerms(clientPath) + if err != nil { + return err + } return output } @@ -307,21 +328,30 @@ func (o *JsonDB) DeleteClient(clientID string) error { func (o *JsonDB) SaveServerInterface(serverInterface model.ServerInterface) error { serverInterfacePath := path.Join(path.Join(o.dbPath, "server"), "interfaces.json") output := o.conn.Write("server", "interfaces", serverInterface) - os.Chmod(serverInterfacePath, 0600) + err := util.ManagePerms(serverInterfacePath) + if err != nil { + return err + } return output } func (o *JsonDB) SaveServerKeyPair(serverKeyPair model.ServerKeypair) error { serverKeyPairPath := path.Join(path.Join(o.dbPath, "server"), "keypair.json") output := o.conn.Write("server", "keypair", serverKeyPair) - os.Chmod(serverKeyPairPath, 0600) + err := util.ManagePerms(serverKeyPairPath) + if err != nil { + return err + } return output } func (o *JsonDB) SaveGlobalSettings(globalSettings model.GlobalSetting) error { globalSettingsPath := path.Join(path.Join(o.dbPath, "server"), "global_settings.json") output := o.conn.Write("server", "global_settings", globalSettings) - os.Chmod(globalSettingsPath, 0600) + err := util.ManagePerms(globalSettingsPath) + if err != nil { + return err + } return output } @@ -337,6 +367,9 @@ func (o *JsonDB) GetHashes() (model.ClientServerHashes, error) { func (o *JsonDB) SaveHashes(hashes model.ClientServerHashes) error { hashesPath := path.Join(path.Join(o.dbPath, "server"), "hashes.json") output := o.conn.Write("server", "hashes", hashes) - os.Chmod(hashesPath, 0600) + err := util.ManagePerms(hashesPath) + if err != nil { + return err + } return output } diff --git a/store/jsondb/jsondb_wake_on_lan.go b/store/jsondb/jsondb_wake_on_lan.go index a0ee935..661ba05 100644 --- a/store/jsondb/jsondb_wake_on_lan.go +++ b/store/jsondb/jsondb_wake_on_lan.go @@ -3,10 +3,10 @@ package jsondb import ( "encoding/json" "fmt" - "os" "path" "github.com/ngoduykhanh/wireguard-ui/model" + "github.com/ngoduykhanh/wireguard-ui/util" ) func (o *JsonDB) GetWakeOnLanHosts() ([]model.WakeOnLanHost, error) { @@ -70,7 +70,11 @@ func (o *JsonDB) SaveWakeOnLanHost(host model.WakeOnLanHost) error { wakeOnLanHostPath := path.Join(path.Join(o.dbPath, model.WakeOnLanHostCollectionName), resourceName+".json") output := o.conn.Write(model.WakeOnLanHostCollectionName, resourceName, host) - os.Chmod(wakeOnLanHostPath, 0600) + err = util.ManagePerms(wakeOnLanHostPath) + if err != nil { + return err + } + return output } diff --git a/util/util.go b/util/util.go index 1203082..f455fc8 100644 --- a/util/util.go +++ b/util/util.go @@ -556,3 +556,8 @@ func RandomString(length int) string { } return string(b) } + +func ManagePerms(path string) error { + err := os.Chmod(path, 0600) + return err +} From e2e1159ef4220acaa9ebcb9c1eafe84ca963fa53 Mon Sep 17 00:00:00 2001 From: Gabriel Soares Date: Mon, 25 Dec 2023 16:21:37 -0300 Subject: [PATCH 05/16] add endpoint field to client (#470) --- handler/routes.go | 1 + model/client.go | 1 + templates/base.html | 7 ++++++- templates/clients.html | 11 ++++++++++- templates/wg.conf | 7 ++++--- 5 files changed, 22 insertions(+), 5 deletions(-) diff --git a/handler/routes.go b/handler/routes.go index c0413d4..3d1982f 100644 --- a/handler/routes.go +++ b/handler/routes.go @@ -645,6 +645,7 @@ func UpdateClient(db store.IStore) echo.HandlerFunc { client.AllocatedIPs = _client.AllocatedIPs client.AllowedIPs = _client.AllowedIPs client.ExtraAllowedIPs = _client.ExtraAllowedIPs + client.Endpoint = _client.Endpoint client.PublicKey = _client.PublicKey client.PresharedKey = _client.PresharedKey client.UpdatedAt = time.Now().UTC() diff --git a/model/client.go b/model/client.go index be5fa76..95342f0 100644 --- a/model/client.go +++ b/model/client.go @@ -15,6 +15,7 @@ type Client struct { AllocatedIPs []string `json:"allocated_ips"` AllowedIPs []string `json:"allowed_ips"` ExtraAllowedIPs []string `json:"extra_allowed_ips"` + Endpoint string `json:"endpoint"` UseServerDNS bool `json:"use_server_dns"` Enabled bool `json:"enabled"` CreatedAt time.Time `json:"created_at"` diff --git a/templates/base.html b/templates/base.html index 0865ff3..88d7568 100644 --- a/templates/base.html +++ b/templates/base.html @@ -232,6 +232,10 @@ +
+ + +
@@ -413,6 +417,7 @@ const email = $("#client_email").val(); const allocated_ips = $("#client_allocated_ips").val().split(","); const allowed_ips = $("#client_allowed_ips").val().split(","); + const endpoint = $("#client_endpoint").val(); let use_server_dns = false; let extra_allowed_ips = []; @@ -434,7 +439,7 @@ const preshared_key = $("#client_preshared_key").val(); const data = {"name": name, "email": email, "allocated_ips": allocated_ips, "allowed_ips": allowed_ips, - "extra_allowed_ips": extra_allowed_ips, "use_server_dns": use_server_dns, "enabled": enabled, + "extra_allowed_ips": extra_allowed_ips, "endpoint": endpoint, "use_server_dns": use_server_dns, "enabled": enabled, "public_key": public_key, "preshared_key": preshared_key}; $.ajax({ diff --git a/templates/clients.html b/templates/clients.html index bcd5855..ae66ecf 100644 --- a/templates/clients.html +++ b/templates/clients.html @@ -113,6 +113,10 @@ Wireguard Clients
+
+ + +
@@ -477,6 +481,8 @@ Wireguard Clients modal.find("#_client_extra_allowed_ips").addTag(obj); }); + modal.find("#_client_endpoint").val(client.endpoint); + modal.find("#_use_server_dns").prop("checked", client.use_server_dns); modal.find("#_enabled").prop("checked", client.enabled); @@ -564,6 +570,8 @@ Wireguard Clients extra_allowed_ips = $("#_client_extra_allowed_ips").val().split(","); } + const endpoint = $("#_client_endpoint").val(); + if ($("#_use_server_dns").is(':checked')){ use_server_dns = true; } @@ -575,7 +583,8 @@ Wireguard Clients } const data = {"id": client_id, "name": name, "email": email, "allocated_ips": allocated_ips, - "allowed_ips": allowed_ips, "extra_allowed_ips": extra_allowed_ips, "use_server_dns": use_server_dns, "enabled": enabled, "public_key": public_key, "preshared_key": preshared_key}; + "allowed_ips": allowed_ips, "extra_allowed_ips": extra_allowed_ips, "endpoint": endpoint, + "use_server_dns": use_server_dns, "enabled": enabled, "public_key": public_key, "preshared_key": preshared_key}; $.ajax({ cache: false, diff --git a/templates/wg.conf b/templates/wg.conf index 81893a8..6b216ce 100644 --- a/templates/wg.conf +++ b/templates/wg.conf @@ -20,6 +20,7 @@ Table = {{ .globalSettings.Table }} # Update at: {{ .Client.UpdatedAt }} [Peer] PublicKey = {{ .Client.PublicKey }} -{{if .Client.PresharedKey }}PresharedKey = {{ .Client.PresharedKey }} -{{end}}AllowedIPs = {{$first :=true}}{{range .Client.AllocatedIPs }}{{if $first}}{{$first = false}}{{else}},{{end}}{{.}}{{end}}{{range .Client.ExtraAllowedIPs }},{{.}}{{end}} -{{end}}{{end}} +{{if .Client.PresharedKey }}PresharedKey = {{ .Client.PresharedKey }}{{end}} +AllowedIPs = {{$first :=true}}{{range .Client.AllocatedIPs }}{{if $first}}{{$first = false}}{{else}},{{end}}{{.}}{{end}}{{range .Client.ExtraAllowedIPs }},{{.}}{{end}}{{end}} +{{if .Client.Endpoint }}Endpoint = {{ .Client.Endpoint }}{{end}} +{{end}} From af7742bfb32bffef98e861dd0eda67f83dabe569 Mon Sep 17 00:00:00 2001 From: Michael Walter <40073144+MiguSchweiz@users.noreply.github.com> Date: Mon, 25 Dec 2023 20:22:42 +0100 Subject: [PATCH 06/16] Update routes.go (#475) use config file download mime type "txt/conf" to prevent downloaded configs being saved as .txt, instead of wanted .conf. Tested on Android Firefox and Chrome --- handler/routes.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handler/routes.go b/handler/routes.go index 3d1982f..aa5461b 100644 --- a/handler/routes.go +++ b/handler/routes.go @@ -729,7 +729,7 @@ func DownloadClient(db store.IStore) echo.HandlerFunc { // set response header for downloading c.Response().Header().Set(echo.HeaderContentDisposition, fmt.Sprintf("attachment; filename=%s.conf", clientData.Client.Name)) - return c.Stream(http.StatusOK, "text/plain", reader) + return c.Stream(http.StatusOK, "text/conf", reader) } } From c8623082fec8997ad8a933de79d0958b69a3df02 Mon Sep 17 00:00:00 2001 From: nebulosa2007 <85841412+nebulosa2007@users.noreply.github.com> Date: Mon, 25 Dec 2023 22:23:51 +0300 Subject: [PATCH 07/16] Make Interface PreDown setting. (#480) --- model/server.go | 1 + templates/server.html | 9 ++++++++- templates/wg.conf | 1 + 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/model/server.go b/model/server.go index 0784eea..0aa804f 100644 --- a/model/server.go +++ b/model/server.go @@ -23,5 +23,6 @@ type ServerInterface struct { ListenPort int `json:"listen_port,string"` // ,string to get listen_port string input as int UpdatedAt time.Time `json:"updated_at"` PostUp string `json:"post_up"` + PreDown string `json:"pre_down"` PostDown string `json:"post_down"` } diff --git a/templates/server.html b/templates/server.html index 366d301..6a24e6e 100644 --- a/templates/server.html +++ b/templates/server.html @@ -42,6 +42,12 @@ Wireguard Server Settings
+
+ + +
+
Date: Tue, 26 Dec 2023 00:25:38 +0500 Subject: [PATCH 08/16] Fixed tag input being too small and unable to fit a CIDR (#483) Co-authored-by: 0xCA --- templates/base.html | 3 +++ templates/clients.html | 3 +++ templates/global_settings.html | 1 + templates/server.html | 1 + 4 files changed, 8 insertions(+) diff --git a/templates/base.html b/templates/base.html index 88d7568..c2fa367 100644 --- a/templates/base.html +++ b/templates/base.html @@ -497,6 +497,7 @@ 'defaultText': 'Add More', 'removeWithBackspace': true, 'minChars': 0, + 'minInputWidth': '100%', 'placeholderColor': '#666666' }); @@ -508,6 +509,7 @@ 'defaultText': 'Add More', 'removeWithBackspace': true, 'minChars': 0, + 'minInputWidth': '100%', 'placeholderColor': '#666666' }); @@ -518,6 +520,7 @@ 'defaultText': 'Add More', 'removeWithBackspace': true, 'minChars': 0, + 'minInputWidth': '100%', 'placeholderColor': '#666666' }); diff --git a/templates/clients.html b/templates/clients.html index ae66ecf..8b4e4ab 100644 --- a/templates/clients.html +++ b/templates/clients.html @@ -427,6 +427,7 @@ Wireguard Clients 'defaultText': 'Add More', 'removeWithBackspace': true, 'minChars': 0, + 'minInputWidth': '100%', 'placeholderColor': '#666666' }); @@ -438,6 +439,7 @@ Wireguard Clients 'defaultText': 'Add More', 'removeWithBackspace': true, 'minChars': 0, + 'minInputWidth': '100%', 'placeholderColor': '#666666' }); @@ -448,6 +450,7 @@ Wireguard Clients 'defaultText': 'Add More', 'removeWithBackspace' : true, 'minChars': 0, + 'minInputWidth': '100%', 'placeholderColor': '#666666' }) diff --git a/templates/global_settings.html b/templates/global_settings.html index cafb630..73b3c93 100644 --- a/templates/global_settings.html +++ b/templates/global_settings.html @@ -203,6 +203,7 @@ Global Settings 'defaultText': 'Add More', 'removeWithBackspace': true, 'minChars': 0, + 'minInputWidth': '100%', 'placeholderColor': '#666666' }); diff --git a/templates/server.html b/templates/server.html index 6a24e6e..e1116a6 100644 --- a/templates/server.html +++ b/templates/server.html @@ -167,6 +167,7 @@ Wireguard Server Settings 'defaultText': 'Add More', 'removeWithBackspace': true, 'minChars': 0, + 'minInputWidth': '100%', 'placeholderColor': '#666666' }); From e73047b14fbcf9799e00641991ba092fb0c3becd Mon Sep 17 00:00:00 2001 From: Vahid Date: Mon, 25 Dec 2023 23:01:11 +0330 Subject: [PATCH 09/16] Feature: Unix domain socket support (#492) Co-authored-by: Khanh Ngo --- .gitignore | 3 +++ README.md | 2 +- main.go | 18 +++++++++++++++++- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index f1e3f44..610b977 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,9 @@ node_modules/ .vscode .idea +# Vim +.*.sw[op] + # Examples examples/docker-compose/config examples/docker-compose/db diff --git a/README.md b/README.md index f7c41f8..d585868 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ docker-compose up | Variable | Description | Default | |-----------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------| | `BASE_PATH` | Set this variable if you run wireguard-ui under a subpath of your reverse proxy virtual host (e.g. /wireguard) | N/A | -| `BIND_ADDRESS` | The addresses that can access to the web interface and the port | 0.0.0.0:80 | +| `BIND_ADDRESS` | The addresses that can access to the web interface and the port, use unix:///abspath/to/file.socket for unix domain socket. | 0.0.0.0:80 | | `SESSION_SECRET` | The secret key used to encrypt the session cookies. Set this to a random value | N/A | | `SESSION_SECRET_FILE` | Optional filepath for the secret key used to encrypt the session cookies. Leave `SESSION_SECRET` blank to take effect | N/A | | `WGUI_USERNAME` | The username for the login page. Used for db initialization only | `admin` | diff --git a/main.go b/main.go index b226ccc..fd4bc90 100644 --- a/main.go +++ b/main.go @@ -8,6 +8,9 @@ import ( "net/http" "os" "time" + "strings" + "net" + "syscall" "github.com/labstack/echo/v4" "github.com/labstack/gommon/log" @@ -229,7 +232,20 @@ func main() { // serves other static files app.GET(util.BasePath+"/static/*", echo.WrapHandler(http.StripPrefix(util.BasePath+"/static/", assetHandler))) - app.Logger.Fatal(app.Start(util.BindAddress)) + if strings.HasPrefix(util.BindAddress, "unix://") { + // Listen on unix domain socket. + // https://github.com/labstack/echo/issues/830 + syscall.Unlink(util.BindAddress[6:]) + l, err := net.Listen("unix", util.BindAddress[6:]) + if err != nil { + app.Logger.Fatal(err) + } + app.Listener = l + app.Logger.Fatal(app.Start("")) + } else { + // Listen on TCP socket + app.Logger.Fatal(app.Start(util.BindAddress)) + } } func initServerConfig(db store.IStore, tmplDir fs.FS) { From a9be53899c292ce3d66361145522016105e2cd01 Mon Sep 17 00:00:00 2001 From: 0xCA <0xCA@users.noreply.github.com> Date: Wed, 27 Dec 2023 13:08:55 +0500 Subject: [PATCH 10/16] Subnet range selector, interface fixes (#481) --- README.md | 1 + custom/js/helper.js | 6 ++ handler/routes.go | 55 ++++++++++-- main.go | 16 ++++ model/client.go | 1 + templates/base.html | 72 +++++++++++++-- templates/clients.html | 124 +++++++++++++++++++++++++- templates/global_settings.html | 1 - templates/server.html | 1 - util/cache.go | 3 + util/config.go | 83 ++++++++++++++---- util/util.go | 154 ++++++++++++++++++++++++++++++++- 12 files changed, 473 insertions(+), 44 deletions(-) create mode 100644 util/cache.go diff --git a/README.md b/README.md index d585868..c43adc3 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,7 @@ docker-compose up | `BIND_ADDRESS` | The addresses that can access to the web interface and the port, use unix:///abspath/to/file.socket for unix domain socket. | 0.0.0.0:80 | | `SESSION_SECRET` | The secret key used to encrypt the session cookies. Set this to a random value | N/A | | `SESSION_SECRET_FILE` | Optional filepath for the secret key used to encrypt the session cookies. Leave `SESSION_SECRET` blank to take effect | N/A | +| `SUBNET_RANGES` | The list of address subdivision ranges. Format: `SR Name:10.0.1.0/24; SR2:10.0.2.0/24,10.0.3.0/24` Each CIDR must be inside one of the server interfaces. | N/A | | `WGUI_USERNAME` | The username for the login page. Used for db initialization only | `admin` | | `WGUI_PASSWORD` | The password for the user on the login page. Will be hashed automatically. Used for db initialization only | `admin` | | `WGUI_PASSWORD_FILE` | Optional filepath for the user login password. Will be hashed automatically. Used for db initialization only. Leave `WGUI_PASSWORD` blank to take effect | N/A | diff --git a/custom/js/helper.js b/custom/js/helper.js index 39bc1fa..4f21c1c 100644 --- a/custom/js/helper.js +++ b/custom/js/helper.js @@ -18,6 +18,11 @@ function renderClientList(data) { allowedIpsHtml += `${obj} `; }) + let subnetRangesString = ""; + if (obj.Client.subnet_ranges && obj.Client.subnet_ranges.length > 0) { + subnetRangesString = obj.Client.subnet_ranges.join(',') + } + // render client html content let html = `
@@ -59,6 +64,7 @@ function renderClientList(data) {
${obj.Client.name} + ${obj.Client.email} ${prettyDateTime(obj.Client.created_at)} diff --git a/handler/routes.go b/handler/routes.go index aa5461b..0358750 100644 --- a/handler/routes.go +++ b/handler/routes.go @@ -366,6 +366,10 @@ func GetClients(db store.IStore) echo.HandlerFunc { }) } + for i, clientData := range clientDataList { + clientDataList[i] = util.FillClientSubnetRange(clientData) + } + return c.JSON(http.StatusOK, clientDataList) } } @@ -391,7 +395,7 @@ func GetClient(db store.IStore) echo.HandlerFunc { return c.JSON(http.StatusNotFound, jsonHTTPResponse{false, "Client not found"}) } - return c.JSON(http.StatusOK, clientData) + return c.JSON(http.StatusOK, util.FillClientSubnetRange(clientData)) } } @@ -988,6 +992,13 @@ func MachineIPAddresses() echo.HandlerFunc { } } +// GetOrderedSubnetRanges handler to get the ordered list of subnet ranges +func GetOrderedSubnetRanges() echo.HandlerFunc { + return func(c echo.Context) error { + return c.JSON(http.StatusOK, util.SubnetRangesOrder) + } +} + // SuggestIPAllocation handler to get the list of ip address for client func SuggestIPAllocation(db store.IStore) echo.HandlerFunc { return func(c echo.Context) error { @@ -1009,22 +1020,48 @@ func SuggestIPAllocation(db store.IStore) echo.HandlerFunc { false, "Cannot suggest ip allocation: failed to get list of allocated ip addresses", }) } - for _, cidr := range server.Interface.Addresses { - ip, err := util.GetAvailableIP(cidr, allocatedIPs) + + sr := c.QueryParam("sr") + searchCIDRList := make([]string, 0) + found := false + + // Use subnet range or default to interface addresses + if util.SubnetRanges[sr] != nil { + for _, cidr := range util.SubnetRanges[sr] { + searchCIDRList = append(searchCIDRList, cidr.String()) + } + } else { + searchCIDRList = append(searchCIDRList, server.Interface.Addresses...) + } + + // Save only unique IPs + ipSet := make(map[string]struct{}) + + for _, cidr := range searchCIDRList { + ip, err := util.GetAvailableIP(cidr, allocatedIPs, server.Interface.Addresses) if err != nil { log.Error("Failed to get available ip from a CIDR: ", err) - return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{ - false, - fmt.Sprintf("Cannot suggest ip allocation: failed to get available ip from network %s", cidr), - }) + continue } + found = true if strings.Contains(ip, ":") { - suggestedIPs = append(suggestedIPs, fmt.Sprintf("%s/128", ip)) + ipSet[fmt.Sprintf("%s/128", ip)] = struct{}{} } else { - suggestedIPs = append(suggestedIPs, fmt.Sprintf("%s/32", ip)) + ipSet[fmt.Sprintf("%s/32", ip)] = struct{}{} } } + if !found { + return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{ + false, + "Cannot suggest ip allocation: failed to get available ip. Try a different subnet or deallocate some ips.", + }) + } + + for ip := range ipSet { + suggestedIPs = append(suggestedIPs, ip) + } + return c.JSON(http.StatusOK, suggestedIPs) } } diff --git a/main.go b/main.go index fd4bc90..5f6e341 100644 --- a/main.go +++ b/main.go @@ -45,6 +45,7 @@ var ( flagSessionSecret string = util.RandomString(32) flagWgConfTemplate string flagBasePath string + flagSubnetRanges string ) const ( @@ -81,6 +82,7 @@ func init() { flag.StringVar(&flagEmailFromName, "email-from-name", util.LookupEnvOrString("EMAIL_FROM_NAME", flagEmailFromName), "'From' email name.") flag.StringVar(&flagWgConfTemplate, "wg-conf-template", util.LookupEnvOrString("WG_CONF_TEMPLATE", flagWgConfTemplate), "Path to custom wg.conf template.") flag.StringVar(&flagBasePath, "base-path", util.LookupEnvOrString("BASE_PATH", flagBasePath), "The base path of the URL") + flag.StringVar(&flagSubnetRanges, "subnet-ranges", util.LookupEnvOrString("SUBNET_RANGES", flagSubnetRanges), "IP ranges to choose from when assigning an IP for a client.") var ( smtpPasswordLookup = util.LookupEnvOrString("SMTP_PASSWORD", flagSmtpPassword) @@ -127,6 +129,7 @@ func init() { util.SessionSecret = []byte(flagSessionSecret) util.WgConfTemplate = flagWgConfTemplate util.BasePath = util.ParseBasePath(flagBasePath) + util.SubnetRanges = util.ParseSubnetRanges(flagSubnetRanges) // print only if log level is INFO or lower if lvl, _ := util.ParseLogLevel(util.LookupEnvOrString(util.LogLevel, "INFO")); lvl <= log.INFO { @@ -145,6 +148,7 @@ func init() { //fmt.Println("Session secret\t:", util.SessionSecret) fmt.Println("Custom wg.conf\t:", util.WgConfTemplate) fmt.Println("Base path\t:", util.BasePath+"/") + fmt.Println("Subnet ranges\t:", util.GetSubnetRangesString()) } } @@ -170,6 +174,17 @@ func main() { // create the wireguard config on start, if it doesn't exist initServerConfig(db, tmplDir) + // Check if subnet ranges are valid for the server configuration + // Remove any non-valid CIDRs + if err := util.ValidateAndFixSubnetRanges(db); err != nil { + panic(err) + } + + // Print valid ranges + if lvl, _ := util.ParseLogLevel(util.LookupEnvOrString(util.LogLevel, "INFO")); lvl <= log.INFO { + fmt.Println("Valid subnet ranges:", util.GetSubnetRangesString()) + } + // register routes app := router.New(tmplDir, extraData, util.SessionSecret) @@ -218,6 +233,7 @@ func main() { app.GET(util.BasePath+"/api/clients", handler.GetClients(db), handler.ValidSession) app.GET(util.BasePath+"/api/client/:id", handler.GetClient(db), handler.ValidSession) app.GET(util.BasePath+"/api/machine-ips", handler.MachineIPAddresses(), handler.ValidSession) + app.GET(util.BasePath+"/api/subnet-ranges", handler.GetOrderedSubnetRanges(), handler.ValidSession) app.GET(util.BasePath+"/api/suggest-client-ips", handler.SuggestIPAllocation(db), handler.ValidSession) app.POST(util.BasePath+"/api/apply-wg-config", handler.ApplyServerConfig(db, tmplDir), handler.ValidSession, handler.ContentTypeJson) app.GET(util.BasePath+"/wake_on_lan_hosts", handler.GetWakeOnLanHosts(db), handler.ValidSession) diff --git a/model/client.go b/model/client.go index 95342f0..187ec72 100644 --- a/model/client.go +++ b/model/client.go @@ -12,6 +12,7 @@ type Client struct { PresharedKey string `json:"preshared_key"` Name string `json:"name"` Email string `json:"email"` + SubnetRanges []string `json:"subnet_ranges,omitempty"` AllocatedIPs []string `json:"allocated_ips"` AllowedIPs []string `json:"allowed_ips"` ExtraAllowedIPs []string `json:"extra_allowed_ips"` diff --git a/templates/base.html b/templates/base.html index c2fa367..3f34140 100644 --- a/templates/base.html +++ b/templates/base.html @@ -58,11 +58,13 @@
@@ -209,6 +211,12 @@
+
+ + +
@@ -368,6 +376,36 @@ $(document).ready(function () { + addGlobalStyle(` +.toast-top-right-fix { + top: 67px; + right: 12px; +} + `, 'toastrToastStyleFix') + + toastr.options.closeDuration = 100; + // toastr.options.timeOut = 10000; + toastr.options.positionClass = 'toast-top-right-fix'; + + updateApplyConfigVisibility() + // from clients.html + updateSearchList() + + }); + + function addGlobalStyle(css, id) { + if (!document.querySelector('#' + id)) { + let head = document.head + if (!head) { return } + let style = document.createElement('style') + style.type = 'text/css' + style.id = id + style.innerHTML = css + head.appendChild(style) + } + } + + function updateApplyConfigVisibility() { $.ajax({ cache: false, method: 'GET', @@ -388,8 +426,7 @@ toastr.error(responseJson['message']); } }); - - }); + } // populateClient function for render new client info @@ -456,6 +493,7 @@ if (window.location.pathname === "{{.basePath}}/") { populateClient(resp.id); } + updateApplyConfigVisibility() }, error: function(jqXHR, exception) { const responseJson = jQuery.parseJSON(jqXHR.responseText); @@ -466,19 +504,32 @@ // updateIPAllocationSuggestion function for automatically fill // the IP Allocation input with suggested ip addresses - function updateIPAllocationSuggestion() { + function updateIPAllocationSuggestion(forceDefault = false) { + let subnetRange = $("#subnet_ranges").select2('val'); + + if (forceDefault || !subnetRange || subnetRange.length === 0) { + subnetRange = '__default_any__' + } $.ajax({ cache: false, method: 'GET', - url: '{{.basePath}}/api/suggest-client-ips', + url: `{{.basePath}}/api/suggest-client-ips?sr=${subnetRange}`, dataType: 'json', contentType: "application/json", success: function(data) { + const allocated_ips = $("#client_allocated_ips").val().split(","); + allocated_ips.forEach(function (item, index) { + $('#client_allocated_ips').removeTag(escape(item)); + }) data.forEach(function (item, index) { $('#client_allocated_ips').addTag(item); }) }, error: function(jqXHR, exception) { + const allocated_ips = $("#client_allocated_ips").val().split(","); + allocated_ips.forEach(function (item, index) { + $('#client_allocated_ips').removeTag(escape(item)); + }) const responseJson = jQuery.parseJSON(jqXHR.responseText); toastr.error(responseJson['message']); } @@ -497,7 +548,6 @@ 'defaultText': 'Add More', 'removeWithBackspace': true, 'minChars': 0, - 'minInputWidth': '100%', 'placeholderColor': '#666666' }); @@ -509,7 +559,6 @@ 'defaultText': 'Add More', 'removeWithBackspace': true, 'minChars': 0, - 'minInputWidth': '100%', 'placeholderColor': '#666666' }); @@ -520,7 +569,6 @@ 'defaultText': 'Add More', 'removeWithBackspace': true, 'minChars': 0, - 'minInputWidth': '100%', 'placeholderColor': '#666666' }); @@ -565,10 +613,17 @@ $("#client_preshared_key").val(""); $("#client_allocated_ips").importTags(''); $("#client_extra_allowed_ips").importTags(''); - updateIPAllocationSuggestion(); + updateSubnetRangesList("#subnet_ranges"); + updateIPAllocationSuggestion(true); }); }); + // handle subnet range select + $('#subnet_ranges').on('select2:select', function (e) { + // console.log('Selected Option: ', $("#subnet_ranges").select2('val')); + updateIPAllocationSuggestion(); + }); + // apply_config_confirm button event $(document).ready(function () { $("#apply_config_confirm").click(function () { @@ -579,6 +634,7 @@ dataType: 'json', contentType: "application/json", success: function(data) { + updateApplyConfigVisibility() $("#modal_apply_config").modal('hide'); toastr.success('Applied config successfully'); }, diff --git a/templates/clients.html b/templates/clients.html index 8b4e4ab..1f8f26d 100644 --- a/templates/clients.html +++ b/templates/clients.html @@ -100,6 +100,12 @@ Wireguard Clients
+
+ + +
@@ -253,13 +259,102 @@ Wireguard Clients setClientStatus(clientID, true); const divElement = document.getElementById("paused_" + clientID); divElement.style.visibility = "hidden"; + updateApplyConfigVisibility() } function pauseClient(clientID) { setClientStatus(clientID, false); const divElement = document.getElementById("paused_" + clientID); divElement.style.visibility = "visible"; + updateApplyConfigVisibility() } + + // updateIPAllocationSuggestion function for automatically fill + // the IP Allocation input with suggested ip addresses + // FOR CHANGING A SUBNET OF AN EXISTING CLIENT + function updateIPAllocationSuggestionExisting() { + let subnetRange = $("#_subnet_ranges").select2('val'); + + if (!subnetRange || subnetRange.length === 0) { + subnetRange = '__default_any__' + } + $.ajax({ + cache: false, + method: 'GET', + url: `{{.basePath}}/api/suggest-client-ips?sr=${subnetRange}`, + dataType: 'json', + contentType: "application/json", + success: function(data) { + const allocated_ips = $("#_client_allocated_ips").val().split(","); + allocated_ips.forEach(function (item, index) { + $('#_client_allocated_ips').removeTag(escape(item)); + }) + data.forEach(function (item, index) { + $('#_client_allocated_ips').addTag(item); + }) + }, + error: function(jqXHR, exception) { + const allocated_ips = $("#_client_allocated_ips").val().split(","); + allocated_ips.forEach(function (item, index) { + $('#_client_allocated_ips').removeTag(escape(item)); + }) + const responseJson = jQuery.parseJSON(jqXHR.responseText); + toastr.error(responseJson['message']); + } + }); + } + + function updateSubnetRangesList(elementID, preselectedVal) { + $.getJSON("{{.basePath}}/api/subnet-ranges", null, function(data) { + $(`${elementID} option`).remove(); + $(elementID).append( + $("") + .text("Any") + .val("__default_any__") + ); + $.each(data, function(index, item) { + $(elementID).append( + $("") + .text(item) + .val(item) + ); + if (item === preselectedVal) { + console.log(preselectedVal); + $(elementID).val(preselectedVal).trigger('change') + } + }); + }); + } + + function updateSearchList() { + $.getJSON("{{.basePath}}/api/subnet-ranges", null, function(data) { + $("#status-selector option").remove(); + $("#status-selector").append( + $("") + .text("All") + .val("All"), + $("") + .text("Enabled") + .val("Enabled"), + $("") + .text("Disabled") + .val("Disabled"), + $("") + .text("Connected") + .val("Connected"), + $("") + .text("Disconnected") + .val("Disconnected") + ); + $.each(data, function(index, item) { + $("#status-selector").append( + $("") + .text(item) + .val(item) + ); + }); + }); +} diff --git a/templates/wg.conf b/templates/wg.conf index 2112a0c..793876e 100644 --- a/templates/wg.conf +++ b/templates/wg.conf @@ -17,6 +17,7 @@ Table = {{ .globalSettings.Table }} # ID: {{ .Client.ID }} # Name: {{ .Client.Name }} # Email: {{ .Client.Email }} +# Telegram: {{ .Client.TgUserid }} # Created at: {{ .Client.CreatedAt }} # Update at: {{ .Client.UpdatedAt }} [Peer] diff --git a/util/cache.go b/util/cache.go index 340f73d..8037c30 100644 --- a/util/cache.go +++ b/util/cache.go @@ -1,3 +1,7 @@ package util +import "sync" + var IPToSubnetRange = map[string]uint16{} +var TgUseridToClientID = map[int64]([]string){} +var TgUseridToClientIDMutex sync.RWMutex diff --git a/util/util.go b/util/util.go index fa168fd..337745a 100644 --- a/util/util.go +++ b/util/util.go @@ -19,6 +19,8 @@ import ( "time" "github.com/ngoduykhanh/wireguard-ui/store" + "github.com/ngoduykhanh/wireguard-ui/telegram" + "github.com/skip2/go-qrcode" "golang.org/x/mod/sumdb/dirhash" externalip "github.com/glendc/go-external-ip" @@ -27,6 +29,12 @@ import ( "github.com/sdomino/scribble" ) +var qrCodeSettings = model.QRCodeSettings{ + Enabled: true, + IncludeDNS: true, + IncludeMTU: true, +} + // BuildClientConfig to create wireguard client config string func BuildClientConfig(client model.Client, server model.Server, setting model.GlobalSetting) string { // Interface section @@ -578,6 +586,57 @@ func WriteWireGuardServerConfig(tmplDir fs.FS, serverConfig model.Server, client return nil } +// SendRequestedConfigsToTelegram to send client all their configs. Returns failed configs list. +func SendRequestedConfigsToTelegram(db store.IStore, userid int64) []string { + failedList := make([]string, 0) + TgUseridToClientIDMutex.RLock() + if clids, found := TgUseridToClientID[userid]; found && len(clids) > 0 { + TgUseridToClientIDMutex.RUnlock() + + for _, clid := range clids { + clientData, err := db.GetClientByID(clid, qrCodeSettings) + if err != nil { + // return fmt.Errorf("unable to get client") + failedList = append(failedList, clid) + continue + } + + // build config + server, _ := db.GetServer() + globalSettings, _ := db.GetGlobalSettings() + config := BuildClientConfig(*clientData.Client, server, globalSettings) + configData := []byte(config) + var qrData []byte + + if clientData.Client.PrivateKey != "" { + qrData, err = qrcode.Encode(config, qrcode.Medium, 512) + if err != nil { + // return fmt.Errorf("unable to encode qr") + failedList = append(failedList, clientData.Client.Name) + continue + } + } + + userid, err := strconv.ParseInt(clientData.Client.TgUserid, 10, 64) + if err != nil { + // return fmt.Errorf("tg usrid is unreadable") + failedList = append(failedList, clientData.Client.Name) + continue + } + + err = telegram.SendConfig(userid, clientData.Client.Name, configData, qrData, true) + if err != nil { + failedList = append(failedList, clientData.Client.Name) + continue + } + time.Sleep(2 * time.Second) + } + } else { + TgUseridToClientIDMutex.RUnlock() + } + return failedList +} + func LookupEnvOrString(key string, defaultVal string) string { if val, ok := os.LookupEnv(key); ok { return val @@ -707,3 +766,65 @@ func ManagePerms(path string) error { err := os.Chmod(path, 0600) return err } + +func AddTgToClientID(userid int64, clientID string) { + TgUseridToClientIDMutex.Lock() + defer TgUseridToClientIDMutex.Unlock() + + if _, ok := TgUseridToClientID[userid]; ok && TgUseridToClientID[userid] != nil { + TgUseridToClientID[userid] = append(TgUseridToClientID[userid], clientID) + } else { + TgUseridToClientID[userid] = []string{clientID} + } +} + +func UpdateTgToClientID(userid int64, clientID string) { + TgUseridToClientIDMutex.Lock() + defer TgUseridToClientIDMutex.Unlock() + + // Detach clientID from any existing userid + for uid, cls := range TgUseridToClientID { + if cls != nil { + filtered := filterStringSlice(cls, clientID) + if len(filtered) > 0 { + TgUseridToClientID[uid] = filtered + } else { + delete(TgUseridToClientID, uid) + } + } + } + + // Attach it to the new one + if _, ok := TgUseridToClientID[userid]; ok && TgUseridToClientID[userid] != nil { + TgUseridToClientID[userid] = append(TgUseridToClientID[userid], clientID) + } else { + TgUseridToClientID[userid] = []string{clientID} + } +} + +func RemoveTgToClientID(clientID string) { + TgUseridToClientIDMutex.Lock() + defer TgUseridToClientIDMutex.Unlock() + + // Detach clientID from any existing userid + for uid, cls := range TgUseridToClientID { + if cls != nil { + filtered := filterStringSlice(cls, clientID) + if len(filtered) > 0 { + TgUseridToClientID[uid] = filtered + } else { + delete(TgUseridToClientID, uid) + } + } + } +} + +func filterStringSlice(s []string, excludedStr string) []string { + filtered := s[:0] + for _, v := range s { + if v != excludedStr { + filtered = append(filtered, v) + } + } + return filtered +} From 4ffd7319f8584dfac08141af7ab2f0418e5de7b2 Mon Sep 17 00:00:00 2001 From: Khanh Ngo Date: Fri, 29 Dec 2023 09:54:51 +0100 Subject: [PATCH 16/16] Upgrade dependencies (#511) - Upgrade GitHub Actions - Upgrade Go version - Upgrade Alpine image --- .github/workflows/docker-build.yml | 8 +- Dockerfile | 4 +- go.mod | 62 ++++-- go.sum | 312 ++++++++--------------------- 4 files changed, 136 insertions(+), 250 deletions(-) diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 0875223..47418d7 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -72,19 +72,19 @@ jobs: # set up docker and build images - name: Set up QEMU - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 - name: Login to Docker Hub - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Build and push - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 with: push: true context: . diff --git a/Dockerfile b/Dockerfile index c6e149f..33da3fa 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Build stage -FROM --platform=${BUILDPLATFORM:-linux/amd64} golang:1.17-alpine3.16 as builder +FROM --platform=${BUILDPLATFORM:-linux/amd64} golang:1.21-alpine3.19 AS builder LABEL maintainer="Khanh Ngo " ARG BUILDPLATFORM @@ -56,7 +56,7 @@ RUN cp -r /build/custom/ assets/ RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -ldflags="-X 'main.appVersion=${APP_VERSION}' -X 'main.buildTime=${BUILD_TIME}' -X 'main.gitCommit=${GIT_COMMIT}'" -a -o wg-ui . # Release stage -FROM alpine:3.16 +FROM alpine:3.19 RUN addgroup -S wgui && \ adduser -S -D -G wgui wgui diff --git a/go.mod b/go.mod index cb3d6b8..ea102f7 100644 --- a/go.mod +++ b/go.mod @@ -1,28 +1,52 @@ module github.com/ngoduykhanh/wireguard-ui -go 1.16 +go 1.21 require ( github.com/NicoNex/echotron/v3 v3.27.0 - github.com/glendc/go-external-ip v0.0.0-20170425150139-139229dcdddd - github.com/go-playground/universal-translator v0.17.0 // indirect - github.com/gorilla/sessions v1.2.0 - github.com/jcelliott/lumber v0.0.0-20160324203708-dd349441af25 // indirect - github.com/labstack/echo-contrib v0.9.0 - github.com/labstack/echo/v4 v4.1.16 - github.com/labstack/gommon v0.3.0 - github.com/leodido/go-urn v1.2.0 // indirect - github.com/rs/xid v1.2.1 + github.com/glendc/go-external-ip v0.1.0 + github.com/gorilla/sessions v1.2.2 + github.com/labstack/echo-contrib v0.15.0 + github.com/labstack/echo/v4 v4.11.4 + github.com/labstack/gommon v0.4.2 + github.com/rs/xid v1.5.0 github.com/sabhiram/go-wol v0.0.0-20211224004021-c83b0c2f887d - github.com/sdomino/scribble v0.0.0-20191024200645-4116320640ba - github.com/sendgrid/rest v2.6.4+incompatible // indirect - github.com/sendgrid/sendgrid-go v3.10.0+incompatible - github.com/skip2/go-qrcode v0.0.0-20191027152451-9434209cb086 - github.com/xhit/go-simple-mail/v2 v2.10.0 - golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 - golang.org/x/mod v0.7.0 + github.com/sdomino/scribble v0.0.0-20230717151034-b95d4df19aa8 + github.com/sendgrid/sendgrid-go v3.14.0+incompatible + github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e + github.com/xhit/go-simple-mail/v2 v2.16.0 + golang.org/x/crypto v0.17.0 + golang.org/x/mod v0.14.0 //golang.zx2c4.com/wireguard v0.0.20200121 // indirect - golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210803171230-4253848d036c - gopkg.in/go-playground/assert.v1 v1.2.1 // indirect + golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 gopkg.in/go-playground/validator.v9 v9.31.0 ) + +require ( + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-test/deep v1.1.0 // indirect + github.com/golang-jwt/jwt v3.2.2+incompatible // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/gorilla/context v1.1.2 // indirect + github.com/gorilla/securecookie v1.1.2 // indirect + github.com/jcelliott/lumber v0.0.0-20160324203708-dd349441af25 // indirect + github.com/josharian/native v1.1.0 // indirect + github.com/leodido/go-urn v1.2.4 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mdlayher/genetlink v1.3.2 // indirect + github.com/mdlayher/netlink v1.7.2 // indirect + github.com/mdlayher/socket v0.5.0 // indirect + github.com/sendgrid/rest v2.6.9+incompatible // indirect + github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasttemplate v1.2.2 // indirect + golang.org/x/net v0.19.0 // indirect + golang.org/x/sync v0.5.0 // indirect + golang.org/x/sys v0.15.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/time v0.5.0 // indirect + golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect + gopkg.in/go-playground/assert.v1 v1.2.1 // indirect +) diff --git a/go.sum b/go.sum index 90023eb..b160f6a 100644 --- a/go.sum +++ b/go.sum @@ -1,257 +1,119 @@ -github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/NicoNex/echotron/v3 v3.27.0 h1:iq4BLPO+Dz1JHjh2HPk0D0NldAZSYcAjaOicgYEhUzw= github.com/NicoNex/echotron/v3 v3.27.0/go.mod h1:LpP5IyHw0y+DZUZMBgXEDAF9O8feXrQu7w7nlJzzoZI= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/appleboy/gofight/v2 v2.1.2/go.mod h1:frW+U1QZEdDgixycTj4CygQ48yLTUhplt43+Wczp3rw= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= -github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/casbin/casbin/v2 v2.0.0/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= -github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/coreos/bbolt v1.3.1-coreos.6.0.20180223184059-4f5275f4ebbf/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= -github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/glendc/go-external-ip v0.0.0-20170425150139-139229dcdddd h1:1BzxHapafGJd/XlpMvocLeDBin2EKn90gXv2AQt5sfo= -github.com/glendc/go-external-ip v0.0.0-20170425150139-139229dcdddd/go.mod h1:o9OoDQyE1WHvYVUH1FdFapy1/rCZHHq3O5wS4VA83ig= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= -github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= -github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= -github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= -github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= -github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= -github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= -github.com/gorilla/sessions v1.1.3/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w= -github.com/gorilla/sessions v1.2.0 h1:S7P+1Hm5V/AT9cjEcUD5uDaQSX0OE577aCXgoaKpYbQ= -github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= +github.com/glendc/go-external-ip v0.1.0 h1:iX3xQ2Q26atAmLTbd++nUce2P5ht5P4uD4V7caSY/xg= +github.com/glendc/go-external-ip v0.1.0/go.mod h1:CNx312s2FLAJoWNdJWZ2Fpf5O4oLsMFwuYviHjS4uJE= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg= +github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= +github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= +github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o= +github.com/gorilla/context v1.1.2/go.mod h1:KDPwT9i/MeWHiLl90fuTgrt4/wPcv75vFAZLaOOcbxM= +github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= +github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= +github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY= +github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ= github.com/jcelliott/lumber v0.0.0-20160324203708-dd349441af25 h1:EFT6MH3igZK/dIVqgGbTqWVvkZ7wJ5iGN03SVtvvdd8= github.com/jcelliott/lumber v0.0.0-20160324203708-dd349441af25/go.mod h1:sWkGw/wsaHtRsT9zGQ/WyJCotGWG/Anow/9hsAcBWRw= github.com/jessevdk/go-flags v0.0.0-20150816100521-1acbbaff2f34/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/josharian/native v0.0.0-20200817173448-b6b71def0850 h1:uhL5Gw7BINiiPAo24A2sxkcDI0Jt/sqp1v5xQCniEFA= -github.com/josharian/native v0.0.0-20200817173448-b6b71def0850/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= -github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw= -github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ= -github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok= -github.com/jsimonetti/rtnetlink v0.0.0-20201216134343-bde56ed16391/go.mod h1:cR77jAZG3Y3bsb8hF6fHJbFoyFukLFOkQ98S0pQz3xw= -github.com/jsimonetti/rtnetlink v0.0.0-20201220180245-69540ac93943/go.mod h1:z4c53zj6Eex712ROyh8WI0ihysb5j2ROyV42iNogmAs= -github.com/jsimonetti/rtnetlink v0.0.0-20210122163228-8d122574c736/go.mod h1:ZXpIyOK59ZnN7J0BV99cZUPmsqDRZ3eq5X+st7u/oSA= -github.com/jsimonetti/rtnetlink v0.0.0-20210212075122-66c871082f2b h1:c3NTyLNozICy8B4mlMXemD3z/gXgQzVXZS/HqT+i3do= -github.com/jsimonetti/rtnetlink v0.0.0-20210212075122-66c871082f2b/go.mod h1:8w9Rh8m+aHZIG69YPGGem1i5VzoyRC8nw2kA8B+ik5U= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/labstack/echo-contrib v0.9.0 h1:hKBA2SnxdxR7sghH0J04zq/pImnKRmgvmQ6MvY9hug4= -github.com/labstack/echo-contrib v0.9.0/go.mod h1:TsFE5Vv0LRpZLoh4mMmaaAxzcTH+1CBFiUtVhwlegzU= -github.com/labstack/echo/v4 v4.1.6/go.mod h1:kU/7PwzgNxZH4das4XNsSpBSOD09XIF5YEPzjpkGnGE= -github.com/labstack/echo/v4 v4.1.16 h1:8swiwjE5Jkai3RPfZoahp8kjVCRNq+y7Q0hPji2Kz0o= -github.com/labstack/echo/v4 v4.1.16/go.mod h1:awO+5TzAjvL8XpibdsfXxPgHr+orhtXZJZIQCVjogKI= -github.com/labstack/gommon v0.2.9/go.mod h1:E8ZTmW9vw5az5/ZyHWCp0Lw4OH2ecsaBP1C/NKavGG4= -github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0= -github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= -github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= -github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= -github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHRmPYs= +github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= +github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= +github.com/labstack/echo-contrib v0.15.0 h1:9K+oRU265y4Mu9zpRDv3X+DGTqUALY6oRHCSZZKCRVU= +github.com/labstack/echo-contrib v0.15.0/go.mod h1:lei+qt5CLB4oa7VHTE0yEfQSEB9XTJI1LUqko9UWvo4= +github.com/labstack/echo/v4 v4.11.4 h1:vDZmA+qNeh1pd/cCkEicDMrjtrnMGQ1QFI9gWN1zGq8= +github.com/labstack/echo/v4 v4.11.4/go.mod h1:noh7EvLwqDsmh/X/HWKPUl1AjzJrhyptRyEbQJfxen8= +github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= +github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= +github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= +github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/mdlayher/ethtool v0.0.0-20210210192532-2b88debcdd43 h1:WgyLFv10Ov49JAQI/ZLUkCZ7VJS3r74hwFIGXJsgZlY= -github.com/mdlayher/ethtool v0.0.0-20210210192532-2b88debcdd43/go.mod h1:+t7E0lkKfbBsebllff1xdTmyJt8lH37niI6kwFk9OTo= -github.com/mdlayher/genetlink v1.0.0 h1:OoHN1OdyEIkScEmRgxLEe2M9U8ClMytqA5niynLtfj0= -github.com/mdlayher/genetlink v1.0.0/go.mod h1:0rJ0h4itni50A86M2kHcgS85ttZazNt7a8H2a2cw0Gc= -github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA= -github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M= -github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY= -github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o= -github.com/mdlayher/netlink v1.2.0/go.mod h1:kwVW1io0AZy9A1E2YYgaD4Cj+C+GPkU6klXCMzIJ9p8= -github.com/mdlayher/netlink v1.2.1/go.mod h1:bacnNlfhqHqqLo4WsYeXSqfyXkInQ9JneWI68v1KwSU= -github.com/mdlayher/netlink v1.2.2-0.20210123213345-5cc92139ae3e/go.mod h1:bacnNlfhqHqqLo4WsYeXSqfyXkInQ9JneWI68v1KwSU= -github.com/mdlayher/netlink v1.3.0/go.mod h1:xK/BssKuwcRXHrtN04UBkwQ6dY9VviGGuriDdoPSWys= -github.com/mdlayher/netlink v1.4.0 h1:n3ARR+Fm0dDv37dj5wSWZXDKcy+U0zwcXS3zKMnSiT0= -github.com/mdlayher/netlink v1.4.0/go.mod h1:dRJi5IABcZpBD2A3D0Mv/AiX8I9uDEu5oGkAVrekmf8= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw= +github.com/mdlayher/genetlink v1.3.2/go.mod h1:tcC3pkCrPUGIKKsCsp0B3AdaaKuHtaxoJRz3cc+528o= +github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g= +github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= +github.com/mdlayher/socket v0.5.0 h1:ilICZmJcQz70vrWVes1MFera4jGiWNocSkykwwoy3XI= +github.com/mdlayher/socket v0.5.0/go.mod h1:WkcBFfvyG8QENs5+hfQPl1X6Jpd2yeLIYgrGFmJiJxI= github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721 h1:RlZweED6sbSArvlE924+mUcZuXKLBHA35U7LN621Bws= github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721/go.mod h1:Ickgr2WtCLZ2MDGd4Gr0geeCH5HybhRJbonOgQpvSxc= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= -github.com/rs/xid v1.2.1 h1:mhH9Nq+C1fY2l1XIpgxIiUOfNpRBYH1kKcr+qfKgjRc= -github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= +github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/sabhiram/go-colorize v0.0.0-20210403184538-366f55d711cf/go.mod h1:GvlEbMJBpbAXFn06UajbdBlGZ18iLvHyuIrgG//L8uk= github.com/sabhiram/go-wol v0.0.0-20211224004021-c83b0c2f887d h1:NDtoSmsxTpDYTqvUurn2ooAzDaYbJSB9/tOhLzaewgo= github.com/sabhiram/go-wol v0.0.0-20211224004021-c83b0c2f887d/go.mod h1:SVPBBd492Gk7Cq5lPd6OAYtIGk2r1FsyH8KT3IB8h7c= -github.com/sdomino/scribble v0.0.0-20191024200645-4116320640ba h1:8QAc9wFAf2b/9cAXskm0wBylObZ0bTpRcaP7ThjLPVQ= -github.com/sdomino/scribble v0.0.0-20191024200645-4116320640ba/go.mod h1:W6zxGUBCXRR5QugSd/nFcFVmwoGnvpjiNY/JwT03Wew= -github.com/sendgrid/rest v2.6.4+incompatible h1:lq6gAQxLwVBf3mVyCCSHI6mgF+NfaJFJHjT0kl6SSo8= -github.com/sendgrid/rest v2.6.4+incompatible/go.mod h1:kXX7q3jZtJXK5c5qK83bSGMdV6tsOE70KbHoqJls4lE= -github.com/sendgrid/sendgrid-go v3.10.0+incompatible h1:aSYyurHxEZSDy7kxhvZ4fH0inNkEEmRssZNbAmETR2c= -github.com/sendgrid/sendgrid-go v3.10.0+incompatible/go.mod h1:QRQt+LX/NmgVEvmdRw0VT/QgUn499+iza2FnDca9fg8= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/skip2/go-qrcode v0.0.0-20191027152451-9434209cb086 h1:RYiqpb2ii2Z6J4x0wxK46kvPBbFuZcdhS+CIztmYgZs= -github.com/skip2/go-qrcode v0.0.0-20191027152451-9434209cb086/go.mod h1:PLPIyL7ikehBD1OAjmKKiOEhbvWyHGaNDjquXMcYABo= +github.com/sdomino/scribble v0.0.0-20230717151034-b95d4df19aa8 h1:hlNRl87eAZhh2QMJVShuXHL6OOd0ObZM0JozDIruNeM= +github.com/sdomino/scribble v0.0.0-20230717151034-b95d4df19aa8/go.mod h1:W6zxGUBCXRR5QugSd/nFcFVmwoGnvpjiNY/JwT03Wew= +github.com/sendgrid/rest v2.6.9+incompatible h1:1EyIcsNdn9KIisLW50MKwmSRSK+ekueiEMJ7NEoxJo0= +github.com/sendgrid/rest v2.6.9+incompatible/go.mod h1:kXX7q3jZtJXK5c5qK83bSGMdV6tsOE70KbHoqJls4lE= +github.com/sendgrid/sendgrid-go v3.14.0+incompatible h1:KDSasSTktAqMJCYClHVE94Fcif2i7P7wzISv1sU6DUA= +github.com/sendgrid/sendgrid-go v3.14.0+incompatible/go.mod h1:QRQt+LX/NmgVEvmdRw0VT/QgUn499+iza2FnDca9fg8= +github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= +github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v0.0.0-20150929183540-2b15294402a8/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/uber-go/atomic v1.4.0/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g= -github.com/uber/jaeger-client-go v2.19.1-0.20191002155754-0be28c34dabf+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= -github.com/uber/jaeger-lib v2.2.0+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208 h1:PM5hJF7HVfNWmCjMdEfbuOBNXSVF2cMFGgQTPdKCbwM= +github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208/go.mod h1:BzWtXXrXzZUvMacR0oF/fbDDgUPO8L36tDMmRAf14ns= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= -github.com/valyala/fasttemplate v1.1.0 h1:RZqt0yGBsps8NGvLSGW804QQqCUYYLsaOjTVHy1Ocw4= -github.com/valyala/fasttemplate v1.1.0/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= -github.com/xhit/go-simple-mail/v2 v2.10.0 h1:nib6RaJ4qVh5HD9UE9QJqnUZyWp3upv+Z6CFxaMj0V8= -github.com/xhit/go-simple-mail/v2 v2.10.0/go.mod h1:kA1XbQfCI4JxQ9ccSN6VFyIEkkugOm7YiPkA5hKiQn4= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20210503195802-e9a32991a82e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA= -golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190607181551-461777fb6f67/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201216054612-986b41b23924/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210504132125-bbd867fde50d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190609082536-301114b31cce/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= +github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +github.com/xhit/go-simple-mail/v2 v2.16.0 h1:ouGy/Ww4kuaqu2E2UrDw7SvLaziWTB60ICLkIkNVccA= +github.com/xhit/go-simple-mail/v2 v2.16.0/go.mod h1:b7P5ygho6SYE+VIqpxA6QkYfv4teeyG4MKqB3utRu98= +golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= +golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201118182958-a01c418693c7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201218084310-7d0127a74742/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210110051926-789bb1bd4061/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210123111255-9b0068b26619/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210216163648-f7da38b97c65/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210309040221-94ec62e08169/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210503173754-0981d6026fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190608022120-eacb66d2a7c3/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.zx2c4.com/wireguard v0.0.0-20210427022245-097af6e1351b h1:XDLXhn7ryprJVo+Lpkiib6CIuXE2031GDwtfEm7vLjI= -golang.zx2c4.com/wireguard v0.0.0-20210427022245-097af6e1351b/go.mod h1:a057zjmoc00UN7gVkaJt2sXVK523kMJcogDTEvPIasg= -golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210803171230-4253848d036c h1:ADNrRDI5NR23/TUCnEmlLZLt4u9DnZ2nwRkPrAcFvto= -golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210803171230-4253848d036c/go.mod h1:+1XihzyZUBJcSc5WO9SwNA7v26puQwOEDwanaxfNXPQ= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4= +golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA= +golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 h1:CawjfCvYQH2OU3/TnxLx97WDSUDRABfT18pCOYwc2GE= +golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6/go.mod h1:3rxYc4HtVcSG9gVaTs2GEBdehh+sYPOwKtyUWEOTb80= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/validator.v9 v9.31.0 h1:bmXmP2RSNtFES+bn4uYuHT7iJFJv7Vj+an+ZQdDaD1M= gopkg.in/go-playground/validator.v9 v9.31.0/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo= -gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=