49 lines
1.5 KiB
Go
49 lines
1.5 KiB
Go
// Package setuptoken provides a one-time setup token used to protect the
|
|
// public initialization endpoints (admin account creation and backup restore)
|
|
// on an uninitialized Portainer instance.
|
|
package setuptoken
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"crypto/subtle"
|
|
"encoding/hex"
|
|
"errors"
|
|
"net/http"
|
|
|
|
httperror "github.com/portainer/portainer/pkg/libhttp/error"
|
|
)
|
|
|
|
// HeaderName is the HTTP header that carries the setup token.
|
|
const HeaderName = "X-Setup-Token"
|
|
|
|
// tokenByteLength is the number of random bytes before hex-encoding (256 bits).
|
|
const tokenByteLength = 32
|
|
|
|
var errInvalidSetupToken = errors.New("invalid or missing setup token")
|
|
|
|
// Generate returns a cryptographically random, hex-encoded setup token.
|
|
func Generate() (string, error) {
|
|
b := make([]byte, tokenByteLength)
|
|
if _, err := rand.Read(b); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return hex.EncodeToString(b), nil
|
|
}
|
|
|
|
// Validate checks that the request carries the expected setup token in the
|
|
// X-Setup-Token header. When expected is empty the gate is disabled and the
|
|
// request is always allowed. Comparison is constant-time.
|
|
func Validate(r *http.Request, expected string) *httperror.HandlerError {
|
|
if expected == "" {
|
|
return nil
|
|
}
|
|
|
|
provided := r.Header.Get(HeaderName)
|
|
if subtle.ConstantTimeCompare([]byte(provided), []byte(expected)) != 1 {
|
|
return httperror.Forbidden("Invalid or missing setup token. Provide the X-Setup-Token header with the token printed in the server logs at startup.", errInvalidSetupToken)
|
|
}
|
|
|
|
return nil
|
|
}
|