fix(worker): harden control-plane lifecycle
Все проверки выполнены успешно
CI / test (push) Successful in 2m32s
Docker / Build and publish worker image (push) Successful in 18m17s
Все проверки выполнены успешно
CI / test (push) Successful in 2m32s
Docker / Build and publish worker image (push) Successful in 18m17s
- reconnect safely after token rotation and retry leased results - reject malformed tasks and remove production cluster debug mutation - validate environment files and require immutable container images BREAKING CHANGE: Docker install, deploy, and Compose now require an immutable repository@sha256 image reference.
Этот коммит содержится в:
@@ -10,13 +10,17 @@ import (
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
var dockerImagePattern = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._/:@-]*$`)
|
||||
var (
|
||||
dockerImagePattern = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._/:-]*@sha256:[a-f0-9]{64}$`)
|
||||
envKeyPattern = regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_]*$`)
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultURL = "https://rsmon.ru"
|
||||
DefaultImage = "reg.rsxx.ru/rsmon/rsmon-worker:latest"
|
||||
DefaultImage = ""
|
||||
binaryPath = "/usr/local/bin/rsmon-worker"
|
||||
envPath = "/etc/rsmon-worker/worker.env"
|
||||
unitPath = "/etc/systemd/system/rsmon-worker.service"
|
||||
@@ -74,7 +78,11 @@ func Install(opts InstallOptions) error {
|
||||
if err := ValidateURL(opts.URL); err != nil {
|
||||
return err
|
||||
}
|
||||
if opts.EnvFile == "" {
|
||||
if opts.EnvFile != "" {
|
||||
if err := ValidateEnvironmentFile(opts.EnvFile); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := ValidateToken(opts.Token); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -156,9 +164,50 @@ func ValidateToken(token string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateEnvironmentFile checks the worker credentials before installation
|
||||
// changes the binary, systemd unit, or Docker image on the host.
|
||||
func ValidateEnvironmentFile(path string) error {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read worker environment: %w", err)
|
||||
}
|
||||
values := make(map[string]string)
|
||||
seenRequired := make(map[string]bool)
|
||||
for number, line := range strings.Split(string(data), "\n") {
|
||||
lineNumber := number + 1
|
||||
if line == "" || strings.HasPrefix(line, "#") {
|
||||
continue
|
||||
}
|
||||
if strings.ContainsRune(line, '\r') {
|
||||
return fmt.Errorf("worker environment line %d contains a carriage return", lineNumber)
|
||||
}
|
||||
key, value, ok := strings.Cut(line, "=")
|
||||
if !ok || !envKeyPattern.MatchString(key) {
|
||||
return fmt.Errorf("worker environment line %d must use KEY=VALUE syntax", lineNumber)
|
||||
}
|
||||
if strings.IndexFunc(value, unicode.IsSpace) >= 0 || strings.ContainsAny(value, "$\\\"'") {
|
||||
return fmt.Errorf("worker environment line %d uses unsupported quoting, interpolation, or whitespace", lineNumber)
|
||||
}
|
||||
if key == "RSMON_URL" || key == "RSMON_TOKEN" {
|
||||
if seenRequired[key] {
|
||||
return fmt.Errorf("worker environment line %d duplicates %s", lineNumber, key)
|
||||
}
|
||||
seenRequired[key] = true
|
||||
}
|
||||
values[key] = value
|
||||
}
|
||||
if err := ValidateURL(values["RSMON_URL"]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ValidateToken(values["RSMON_TOKEN"]); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ValidateImage(image string) error {
|
||||
if !dockerImagePattern.MatchString(image) {
|
||||
return errors.New("Docker image must be one non-option argument")
|
||||
return errors.New("Docker image must be an immutable repository@sha256:<64 lowercase hex characters> reference")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Ссылка в новой задаче
Block a user