Initial logic to support logging activity on Wings to send back to the panel
This commit is contained in:
parent
214baf83fb
commit
9eab08b92f
38
database/database.go
Normal file
38
database/database.go
Normal file
|
@ -0,0 +1,38 @@
|
|||
package database
|
||||
|
||||
import (
|
||||
"emperror.dev/errors"
|
||||
"github.com/apex/log"
|
||||
"github.com/pterodactyl/wings/config"
|
||||
"github.com/xujiajun/nutsdb"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var db *nutsdb.DB
|
||||
var syncer sync.Once
|
||||
|
||||
var (
|
||||
ServerEventsBucket = "server_events"
|
||||
)
|
||||
|
||||
func initialize() error {
|
||||
opt := nutsdb.DefaultOptions
|
||||
opt.Dir = filepath.Join(config.Get().System.RootDirectory, "db")
|
||||
|
||||
instance, err := nutsdb.Open(opt)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
db = instance
|
||||
return nil
|
||||
}
|
||||
|
||||
func DB() *nutsdb.DB {
|
||||
syncer.Do(func() {
|
||||
if err := initialize(); err != nil {
|
||||
log.WithField("error", err).Fatal("database: failed to initialize instance, this is an unrecoverable error")
|
||||
}
|
||||
})
|
||||
return db
|
||||
}
|
8
go.mod
8
go.mod
|
@ -37,7 +37,7 @@ require (
|
|||
github.com/pkg/sftp v1.13.4
|
||||
github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06
|
||||
github.com/spf13/cobra v1.4.0
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/stretchr/testify v1.7.1
|
||||
golang.org/x/crypto v0.0.0-20220321153916-2c7772ba3064
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||
gopkg.in/ini.v1 v1.66.4
|
||||
|
@ -46,7 +46,7 @@ require (
|
|||
|
||||
require github.com/goccy/go-json v0.9.6
|
||||
|
||||
require golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8 // indirect
|
||||
require golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e // indirect
|
||||
|
||||
require (
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||
|
@ -54,6 +54,7 @@ require (
|
|||
github.com/Microsoft/hcsshim v0.9.2 // indirect
|
||||
github.com/andybalholm/brotli v1.0.4 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bwmarrin/snowflake v0.3.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/containerd/containerd v1.6.2 // indirect
|
||||
github.com/containerd/fifo v1.0.0 // indirect
|
||||
|
@ -101,6 +102,9 @@ require (
|
|||
github.com/ugorji/go/codec v1.2.7 // indirect
|
||||
github.com/ulikunitz/xz v0.5.10 // indirect
|
||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
|
||||
github.com/xujiajun/mmap-go v1.0.1 // indirect
|
||||
github.com/xujiajun/nutsdb v0.9.0 // indirect
|
||||
github.com/xujiajun/utils v0.0.0-20190123093513-8bf096c4f53b // indirect
|
||||
go.uber.org/atomic v1.9.0 // indirect
|
||||
go.uber.org/multierr v1.8.0 // indirect
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect
|
||||
|
|
15
go.sum
15
go.sum
|
@ -151,6 +151,8 @@ github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx2
|
|||
github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8=
|
||||
github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50=
|
||||
github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE=
|
||||
github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0=
|
||||
github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE=
|
||||
github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
|
||||
github.com/cenkalti/backoff/v4 v4.1.2 h1:6Yo7N8UP2K6LWZnW94DLVSSrbobcWdVzAYOisuDPIFo=
|
||||
github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
|
||||
|
@ -940,6 +942,8 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
|
|||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||
github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||
|
@ -988,6 +992,13 @@ github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofm
|
|||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
github.com/xujiajun/gorouter v1.2.0/go.mod h1:yJrIta+bTNpBM/2UT8hLOaEAFckO+m/qmR3luMIQygM=
|
||||
github.com/xujiajun/mmap-go v1.0.1 h1:7Se7ss1fLPPRW+ePgqGpCkfGIZzJV6JPq9Wq9iv/WHc=
|
||||
github.com/xujiajun/mmap-go v1.0.1/go.mod h1:CNN6Sw4SL69Sui00p0zEzcZKbt+5HtEnYUsc6BKKRMg=
|
||||
github.com/xujiajun/nutsdb v0.9.0 h1:vy8rjDp0Sk/SnTAqg61i+G4NIN/3tBKSdZ6rIyKYVIo=
|
||||
github.com/xujiajun/nutsdb v0.9.0/go.mod h1:8ZdTTF0cEQO+wN940htfHYKswFql2iB6Osckx+GmOoU=
|
||||
github.com/xujiajun/utils v0.0.0-20190123093513-8bf096c4f53b h1:jKG9OiL4T4xQN3IUrhUpc1tG+HfDXppkgVcrAiiaI/0=
|
||||
github.com/xujiajun/utils v0.0.0-20190123093513-8bf096c4f53b/go.mod h1:AZd87GYJlUzl82Yab2kTjx1EyXSQCAfZDhpTo1SQC4k=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
|
@ -1199,6 +1210,7 @@ golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5h
|
|||
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/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-20181221143128-b4a75ba826a6/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=
|
||||
|
@ -1296,6 +1308,9 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8 h1:OH54vjqzRWmbJ62fjuhxy7AxFFgoHN0/DPc/UrL8cAs=
|
||||
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220405210540-1e041c57c461/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e h1:CsOuNlbOuf0mzxJIefr6Q4uAUetRUwZE4qt7VfzP+xo=
|
||||
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/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-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
|
|
|
@ -66,6 +66,7 @@ func Configure(m *wserver.Manager, client remote.Client) *gin.Engine {
|
|||
server.DELETE("", deleteServer)
|
||||
|
||||
server.GET("/logs", getServerLogs)
|
||||
server.GET("/activity", getServerActivityLogs)
|
||||
server.POST("/power", postServerPower)
|
||||
server.POST("/commands", postServerCommands)
|
||||
server.POST("/install", postServerInstall)
|
||||
|
|
|
@ -2,6 +2,9 @@ package router
|
|||
|
||||
import (
|
||||
"context"
|
||||
"github.com/goccy/go-json"
|
||||
"github.com/pterodactyl/wings/database"
|
||||
"github.com/xujiajun/nutsdb"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
|
@ -40,6 +43,44 @@ func getServerLogs(c *gin.Context) {
|
|||
c.JSON(http.StatusOK, gin.H{"data": out})
|
||||
}
|
||||
|
||||
// Returns the activity logs tracked internally for the server instance. Note that these
|
||||
// logs are routinely cleared out as Wings communicates directly with the Panel to pass
|
||||
// along all of the logs for servers it monitors. As activities are passed to the panel
|
||||
// they are deleted from Wings.
|
||||
//
|
||||
// As a result, this endpoint may or may not return data, and the data returned can change
|
||||
// between requests.
|
||||
func getServerActivityLogs(c *gin.Context) {
|
||||
s := ExtractServer(c)
|
||||
|
||||
var out [][]byte
|
||||
err := database.DB().View(func(tx *nutsdb.Tx) error {
|
||||
items, err := tx.LRange(database.ServerEventsBucket, []byte(s.ID()), 0, 10)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
out = items
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
middleware.CaptureAndAbort(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
var activity []*server.Activity
|
||||
for _, b := range out {
|
||||
var a server.Activity
|
||||
if err := json.Unmarshal(b, &a); err != nil {
|
||||
middleware.CaptureAndAbort(c, err)
|
||||
return
|
||||
}
|
||||
activity = append(activity, &a)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"data": activity})
|
||||
}
|
||||
|
||||
// Handles a request to control the power state of a server. If the action being passed
|
||||
// through is invalid a 404 is returned. Otherwise, a HTTP/202 Accepted response is returned
|
||||
// and the actual power action is run asynchronously so that we don't have to block the
|
||||
|
|
|
@ -7,7 +7,6 @@ import (
|
|||
|
||||
"github.com/apex/log"
|
||||
"github.com/gbrlsnchs/jwt/v3"
|
||||
"github.com/goccy/go-json"
|
||||
)
|
||||
|
||||
// The time at which Wings was booted. No JWT's created before this time are allowed to
|
||||
|
@ -35,15 +34,15 @@ func DenyJTI(jti string) {
|
|||
denylist.Store(jti, time.Now())
|
||||
}
|
||||
|
||||
// A JWT payload for Websocket connections. This JWT is passed along to the Websocket after
|
||||
// it has been connected to by sending an "auth" event.
|
||||
// WebsocketPayload defines the JWT payload for a websocket connection. This JWT is passed along to
|
||||
// the websocket after it has been connected to by sending an "auth" event.
|
||||
type WebsocketPayload struct {
|
||||
jwt.Payload
|
||||
sync.RWMutex
|
||||
|
||||
UserID json.Number `json:"user_id"`
|
||||
ServerUUID string `json:"server_uuid"`
|
||||
Permissions []string `json:"permissions"`
|
||||
UserUUID string `json:"user_uuid"`
|
||||
ServerUUID string `json:"server_uuid"`
|
||||
Permissions []string `json:"permissions"`
|
||||
}
|
||||
|
||||
// Returns the JWT payload.
|
||||
|
|
|
@ -40,6 +40,7 @@ type Handler struct {
|
|||
Connection *websocket.Conn `json:"-"`
|
||||
jwt *tokens.WebsocketPayload
|
||||
server *server.Server
|
||||
ra server.RequestActivity
|
||||
uuid uuid.UUID
|
||||
}
|
||||
|
||||
|
@ -109,6 +110,7 @@ func GetHandler(s *server.Server, w http.ResponseWriter, r *http.Request) (*Hand
|
|||
Connection: conn,
|
||||
jwt: nil,
|
||||
server: s,
|
||||
ra: s.NewRequestActivity("", r.RemoteAddr),
|
||||
uuid: u,
|
||||
}, nil
|
||||
}
|
||||
|
@ -264,6 +266,7 @@ func (h *Handler) GetJwt() *tokens.WebsocketPayload {
|
|||
// setJwt sets the JWT for the websocket in a race-safe manner.
|
||||
func (h *Handler) setJwt(token *tokens.WebsocketPayload) {
|
||||
h.Lock()
|
||||
h.ra = h.ra.SetUser(token.UserUUID)
|
||||
h.jwt = token
|
||||
h.Unlock()
|
||||
}
|
||||
|
@ -421,6 +424,15 @@ func (h *Handler) HandleInbound(ctx context.Context, m Message) error {
|
|||
}
|
||||
}
|
||||
|
||||
// Track this command sending event in the local database.
|
||||
e := h.ra.Event(server.ActivityCommandSent, server.ActivityMeta{
|
||||
"command": strings.Join(m.Args, ""),
|
||||
})
|
||||
|
||||
if err := e.Save(); err != nil {
|
||||
h.server.Log().WithField("error", err).Error("activity: failed to persist event to database")
|
||||
}
|
||||
|
||||
return h.server.Environment.SendCommand(strings.Join(m.Args, ""))
|
||||
}
|
||||
}
|
||||
|
|
110
server/activity.go
Normal file
110
server/activity.go
Normal file
|
@ -0,0 +1,110 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"emperror.dev/errors"
|
||||
"github.com/apex/log"
|
||||
"github.com/goccy/go-json"
|
||||
"github.com/pterodactyl/wings/database"
|
||||
"github.com/xujiajun/nutsdb"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Event string
|
||||
type ActivityMeta map[string]interface{}
|
||||
|
||||
const (
|
||||
ActivityCommandSent = Event("command.sent")
|
||||
)
|
||||
|
||||
type Activity struct {
|
||||
// User is UUID of the user that triggered this event, or an empty string if the event
|
||||
// cannot be tied to a specific user, in which case we will assume it was the system
|
||||
// user.
|
||||
User string `json:"user"`
|
||||
// Server is the UUID of the server this event is associated with.
|
||||
Server string `json:"server"`
|
||||
// Event is a string that describes what occurred, and is used by the Panel instance to
|
||||
// properly associate this event in the activity logs.
|
||||
Event Event `json:"event"`
|
||||
// Metadata is either a null value, string, or a JSON blob with additional event specific
|
||||
// metadata that can be provided.
|
||||
Metadata ActivityMeta `json:"metadata"`
|
||||
// IP is the IP address that triggered this event, or an empty string if it cannot be
|
||||
// determined properly.
|
||||
IP string `json:"ip"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
}
|
||||
|
||||
// RequestActivity is a wrapper around a LoggedEvent that is able to track additional request
|
||||
// specific metadata including the specific user and IP address associated with all subsequent
|
||||
// events. The internal logged event structure can be extracted by calling RequestEvent.Event().
|
||||
type RequestActivity struct {
|
||||
server string
|
||||
user string
|
||||
ip string
|
||||
}
|
||||
|
||||
// Event returns the underlying logged event from the RequestEvent instance and sets the
|
||||
// specific event and metadata on it.
|
||||
func (ra RequestActivity) Event(event Event, metadata ActivityMeta) Activity {
|
||||
return Activity{
|
||||
User: ra.user,
|
||||
Server: ra.server,
|
||||
IP: ra.ip,
|
||||
Event: event,
|
||||
Metadata: metadata,
|
||||
}
|
||||
}
|
||||
|
||||
// IP returns the IP address associated with this entry.
|
||||
func (ra RequestActivity) IP() string {
|
||||
return ra.ip
|
||||
}
|
||||
|
||||
// SetUser clones the RequestActivity struct and sets a new user value on the copy
|
||||
// before returning it.
|
||||
func (ra RequestActivity) SetUser(u string) RequestActivity {
|
||||
c := ra
|
||||
c.user = u
|
||||
return c
|
||||
}
|
||||
|
||||
// Save logs the provided event using Wings' internal K/V store so that we can then
|
||||
// pass it along to the Panel at set intervals. In addition, this will ensure that the events
|
||||
// are persisted to the disk, even between instance restarts.
|
||||
func (a Activity) Save() error {
|
||||
if a.Timestamp.IsZero() {
|
||||
a.Timestamp = time.Now().UTC()
|
||||
}
|
||||
|
||||
value, err := json.Marshal(a)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "database: failed to marshal activity into json bytes")
|
||||
}
|
||||
|
||||
return database.DB().Update(func(tx *nutsdb.Tx) error {
|
||||
log.WithField("subsystem", "activity").
|
||||
WithFields(log.Fields{"server": a.Server, "user": a.User, "event": a.Event, "ip": a.IP}).
|
||||
Debug("saving activity to database")
|
||||
|
||||
if err := tx.RPush(database.ServerEventsBucket, []byte(a.Server), value); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Server) NewRequestActivity(user string, ip string) RequestActivity {
|
||||
return RequestActivity{server: s.ID(), user: user, ip: ip}
|
||||
}
|
||||
|
||||
// NewActivity creates a new event instance for the server in question.
|
||||
func (s *Server) NewActivity(user string, event Event, metadata ActivityMeta, ip string) Activity {
|
||||
return Activity{
|
||||
User: user,
|
||||
Server: s.ID(),
|
||||
Event: event,
|
||||
Metadata: metadata,
|
||||
IP: ip,
|
||||
}
|
||||
}
|
20
system/strings.go
Normal file
20
system/strings.go
Normal file
|
@ -0,0 +1,20 @@
|
|||
package system
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"
|
||||
|
||||
// RandomString generates a random string of alpha-numeric characters using a
|
||||
// pseudo-random number generator. The output of this function IS NOT cryptographically
|
||||
// secure, it is used solely for generating random strings outside a security context.
|
||||
func RandomString(n int) string {
|
||||
var b strings.Builder
|
||||
b.Grow(n)
|
||||
for i := 0; i < n; i++ {
|
||||
b.WriteByte(characters[rand.Intn(len(characters))])
|
||||
}
|
||||
return b.String()
|
||||
}
|
8
wings.go
8
wings.go
|
@ -2,8 +2,16 @@ package main
|
|||
|
||||
import (
|
||||
"github.com/pterodactyl/wings/cmd"
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Since we make use of the math/rand package in the code, especially for generating
|
||||
// non-cryptographically secure random strings we need to seed the RNG. Just make use
|
||||
// of the current time for this.
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
|
||||
// Execute the main binary code.
|
||||
cmd.Execute()
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user