diff --git a/cmd/root.go b/cmd/root.go index 4bb5f45..0bb0388 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -125,9 +125,10 @@ func rootCmdRun(cmd *cobra.Command, _ []string) { return } + t := config.Get().Token pclient := remote.New( config.Get().PanelLocation, - remote.WithCredentials(config.Get().AuthenticationTokenId, config.Get().AuthenticationToken), + remote.WithCredentials(t.ID, t.Token), remote.WithHttpClient(&http.Client{ Timeout: time.Second * time.Duration(config.Get().RemoteQuery.Timeout), }), diff --git a/config/config.go b/config/config.go index ab7ad37..254802b 100644 --- a/config/config.go +++ b/config/config.go @@ -1,6 +1,7 @@ package config import ( + "bytes" "context" "crypto/tls" "fmt" @@ -294,7 +295,14 @@ type ConsoleThrottles struct { Period uint64 `json:"line_reset_interval" yaml:"line_reset_interval" default:"100"` } +type Token struct { + ID string + Token string +} + type Configuration struct { + Token Token `json:"-"` + // The location from which this configuration instance was instantiated. path string @@ -564,6 +572,26 @@ func FromFile(path string) error { return err } + c.Token = Token{ + ID: os.Getenv("WINGS_TOKEN_ID"), + Token: os.Getenv("WINGS_TOKEN"), + } + if c.Token.ID == "" { + c.Token.ID = c.AuthenticationTokenId + } + if c.Token.Token == "" { + c.Token.Token = c.AuthenticationToken + } + + c.Token.ID, err = Expand(c.Token.ID) + if err != nil { + return err + } + c.Token.Token, err = Expand(c.Token.Token) + if err != nil { + return err + } + // Store this configuration in the global state. Set(c) return nil @@ -766,3 +794,36 @@ func UseOpenat2() bool { return true } } + +// Expand expands an input string by calling [os.ExpandEnv] to expand all +// environment variables, then checks if the value is prefixed with `file://` +// to support reading the value from a file. +// +// NOTE: the order of expanding environment variables first then checking if +// the value references a file is important. This behaviour allows a user to +// pass a value like `file://${CREDENTIALS_DIRECTORY}/token` to allow us to +// work with credentials loaded by systemd's `LoadCredential` (or `LoadCredentialEncrypted`) +// options without the user needing to assume the path of `CREDENTIALS_DIRECTORY` +// or use a preStart script to read the files for us. +func Expand(v string) (string, error) { + // Expand environment variables within the string. + // + // NOTE: this may cause issues if the string contains `$` and doesn't intend + // on getting expanded, however we are using this for our tokens which are + // all alphanumeric characters only. + v = os.ExpandEnv(v) + + // Handle files. + const filePrefix = "file://" + if strings.HasPrefix(v, filePrefix) { + p := v[len(filePrefix):] + + b, err := os.ReadFile(p) + if err != nil { + return "", nil + } + v = string(bytes.TrimRight(bytes.TrimRight(b, "\r"), "\n")) + } + + return v, nil +} diff --git a/router/middleware/middleware.go b/router/middleware/middleware.go index cd37a9e..1908711 100644 --- a/router/middleware/middleware.go +++ b/router/middleware/middleware.go @@ -168,7 +168,6 @@ func RequireAuthorization() gin.HandlerFunc { // We don't put this value outside this function since the node's authentication // token can be changed on the fly and the config.Get() call returns a copy, so // if it is rotated this value will never properly get updated. - token := config.Get().AuthenticationToken auth := strings.SplitN(c.GetHeader("Authorization"), " ", 2) if len(auth) != 2 || auth[0] != "Bearer" { c.Header("WWW-Authenticate", "Bearer") @@ -179,7 +178,7 @@ func RequireAuthorization() gin.HandlerFunc { // All requests to Wings must be authorized with the authentication token present in // the Wings configuration file. Remeber, all requests to Wings come from the Panel // backend, or using a signed JWT for temporary authentication. - if subtle.ConstantTimeCompare([]byte(auth[1]), []byte(token)) != 1 { + if subtle.ConstantTimeCompare([]byte(auth[1]), []byte(config.Get().Token.Token)) != 1 { c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": "You are not authorized to access this endpoint."}) return }