Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 30 additions & 18 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,34 @@ func Set(c *Configuration) {
_config = c
}

// ResolveToken populates the derived Token field, preferring values pinned
// through the environment over those in the configuration itself.
//
// Set remote when the values came from the Panel. Local values may use
// "file://" or "$VAR" indirection; expanding one sent over the network would
// leak files and environment variables back out through the token we attach to
// every request.
func (c *Configuration) ResolveToken(remote bool) error {
resolve := func(env, local string) (string, error) {
if env != "" {
return Expand(env)
}
if remote {
return local, nil
}
return Expand(local)
}

var err error
if c.Token.ID, err = resolve(os.Getenv("WINGS_TOKEN_ID"), c.AuthenticationTokenId); err != nil {
return err
}
if c.Token.Token, err = resolve(os.Getenv("WINGS_TOKEN"), c.AuthenticationToken); err != nil {
return err
}
return nil
}

// SetDebugViaFlag tracks if the application is running in debug mode because of
// a command line flag argument. If so we do not want to store that configuration
// change to the disk.
Expand Down Expand Up @@ -600,23 +628,7 @@ 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 {
if err := c.ResolveToken(false); err != nil {
return err
}

Expand Down Expand Up @@ -860,7 +872,7 @@ func Expand(v string) (string, error) {

b, err := os.ReadFile(p)
if err != nil {
return "", nil
return "", err
}
v = string(bytes.TrimRight(bytes.TrimRight(b, "\r"), "\n"))
}
Expand Down
24 changes: 22 additions & 2 deletions remote/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"net/http"
"strconv"
"strings"
"sync"
"time"

"github.com/pterodactyl/wings/internal/models"
Expand All @@ -33,11 +34,13 @@ type Client interface {
SetTransferStatus(ctx context.Context, uuid string, successful bool) error
ValidateSftpCredentials(ctx context.Context, request SftpAuthRequest) (SftpAuthResponse, error)
SendActivityLogs(ctx context.Context, activity []models.Activity) error
SetCredentials(id, token string)
}

type client struct {
httpClient *http.Client
baseUrl string
mu sync.RWMutex
tokenId string
token string
maxAttempts int
Expand Down Expand Up @@ -68,6 +71,22 @@ func WithCredentials(id, token string) ClientOption {
}
}

// SetCredentials replaces the credentials used when making requests to the
// remote API endpoint.
func (c *client) SetCredentials(id, token string) {
c.mu.Lock()
defer c.mu.Unlock()
c.tokenId = id
c.token = token
}

// credentials returns the credentials currently in use by this client.
func (c *client) credentials() (string, string) {
c.mu.RLock()
defer c.mu.RUnlock()
return c.tokenId, c.token
}

// WithHttpClient sets the underlying HTTP client instance to use when making
// requests to the Panel API.
func WithHttpClient(httpClient *http.Client) ClientOption {
Expand Down Expand Up @@ -105,10 +124,11 @@ func (c *client) requestOnce(ctx context.Context, method, path string, body io.R
return nil, err
}

req.Header.Set("User-Agent", fmt.Sprintf("Pterodactyl Wings/v%s (id:%s)", system.Version, c.tokenId))
tokenId, token := c.credentials()
req.Header.Set("User-Agent", fmt.Sprintf("Pterodactyl Wings/v%s (id:%s)", system.Version, tokenId))
req.Header.Set("Accept", "application/vnd.pterodactyl.v1+json")
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s.%s", c.tokenId, c.token))
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s.%s", tokenId, token))

// Call all opts functions to allow modifying the request
for _, o := range opts {
Expand Down
2 changes: 2 additions & 0 deletions router/router_server_backup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ func (c backupTestRemoteClient) SendActivityLogs(context.Context, []models.Activ
return nil
}

func (c backupTestRemoteClient) SetCredentials(_, _ string) {}

type backupTestEnvironment struct{}

func (backupTestEnvironment) Type() string { return "test" }
Expand Down
21 changes: 21 additions & 0 deletions router/router_system.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,22 @@ func postUpdateConfiguration(c *gin.Context) {
cfg.Api.Ssl.CertificateFile = config.Get().Api.Ssl.CertificateFile
}

// The token that everything authenticates against is a derived value that is
// not part of the payload sent by the Panel, so it has to be re-resolved from
// the new token values.
if err := cfg.ResolveToken(true); err != nil {
middleware.CaptureAndAbort(c, err)
return
}

// Refuse to go any further with a token we could never authenticate against.
if cfg.Token.ID == "" || cfg.Token.Token == "" {
middleware.CaptureAndAbort(c, errors.New("config: refusing to apply an update with an empty authentication token"))
return
}

tokenId, token := cfg.Token.ID, cfg.Token.Token

// Try to write this new configuration to the disk before updating our global
// state with it.
if err := config.WriteToDisk(cfg); err != nil {
Expand All @@ -152,6 +168,11 @@ func postUpdateConfiguration(c *gin.Context) {
// Since we wrote it to the disk successfully now update the global configuration
// state to use this new configuration struct.
config.Set(cfg)

// Requests we make back to the Panel use credentials that were captured when
// the client was created at boot, so they have to be rotated explicitly.
middleware.ExtractManager(c).Client().SetCredentials(tokenId, token)

c.JSON(http.StatusOK, postUpdateConfigurationResponse{
Applied: true,
})
Expand Down