init
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultMode = "smoke"
|
||||
DefaultPlatformURL = "http://127.0.0.1:8080"
|
||||
DefaultEndpointID = "run-local"
|
||||
DefaultDisplayName = "Local Run"
|
||||
DefaultVersion = "0.1.0"
|
||||
)
|
||||
|
||||
var BuildVersion = DefaultVersion
|
||||
var BuildMode string
|
||||
var BuildPlatformURL string
|
||||
var BuildRunEndpointID string
|
||||
var BuildDisplayName string
|
||||
var BuildRegistrationToken string
|
||||
var BuildServerInstanceID string
|
||||
var BuildPluginID string
|
||||
var BuildComponentKind string
|
||||
var BuildComponentKey string
|
||||
var BuildKeyGeneration string
|
||||
var BuildWorkspaceSeed string
|
||||
|
||||
type Config struct {
|
||||
Mode string
|
||||
PlatformURL string
|
||||
RunEndpointID string
|
||||
DisplayName string
|
||||
Version string
|
||||
RegistrationToken string
|
||||
ServerInstanceID string
|
||||
PluginID string
|
||||
ComponentKind string
|
||||
ComponentKey string
|
||||
KeyGeneration int
|
||||
SecretRef string
|
||||
WorkspaceSeed string
|
||||
WorkspaceRoot string
|
||||
BuildSourceRoot string
|
||||
SpoolRoot string
|
||||
MaxJobs int
|
||||
HeartbeatInterval time.Duration
|
||||
PollInterval time.Duration
|
||||
RetryBackoff time.Duration
|
||||
UpdateJobID string
|
||||
UpdateOutcome string
|
||||
UpdateAttempt int
|
||||
UpdateLeaseToken string
|
||||
UpdateHealthFile string
|
||||
LocalStartupDiagnostics bool
|
||||
}
|
||||
|
||||
func Load() Config {
|
||||
mode := os.Getenv("RUN_MODE")
|
||||
if mode == "" {
|
||||
mode = stringOrDefault(BuildMode, DefaultMode)
|
||||
}
|
||||
|
||||
platformURL := os.Getenv("RUN_PLATFORM_URL")
|
||||
if platformURL == "" {
|
||||
platformURL = stringOrDefault(BuildPlatformURL, DefaultPlatformURL)
|
||||
}
|
||||
|
||||
workspaceRoot := envOrDefault("RUN_WORKSPACE_ROOT", filepath.Join(".", ".run-workspace"))
|
||||
return Config{
|
||||
Mode: mode,
|
||||
PlatformURL: platformURL,
|
||||
RunEndpointID: envOrDefault("RUN_ENDPOINT_ID", stringOrDefault(BuildRunEndpointID, DefaultEndpointID)),
|
||||
DisplayName: envOrDefault("RUN_DISPLAY_NAME", stringOrDefault(BuildDisplayName, DefaultDisplayName)),
|
||||
Version: envOrDefault("RUN_VERSION", BuildVersion),
|
||||
RegistrationToken: envOrDefault("RUN_REGISTRATION_TOKEN", stringOrDefault(BuildRegistrationToken, "local-registration")),
|
||||
ServerInstanceID: envOrDefault("RUN_SERVER_INSTANCE_ID", BuildServerInstanceID),
|
||||
PluginID: envOrDefault("RUN_PLUGIN_ID", BuildPluginID),
|
||||
ComponentKind: envOrDefault("RUN_COMPONENT_KIND", BuildComponentKind),
|
||||
ComponentKey: envOrDefault("RUN_COMPONENT_KEY", BuildComponentKey),
|
||||
KeyGeneration: intEnvOrDefault("RUN_KEY_GENERATION", intStringOrDefault(BuildKeyGeneration, 0)),
|
||||
WorkspaceSeed: envOrDefault("RUN_WORKSPACE_SEED", BuildWorkspaceSeed),
|
||||
WorkspaceRoot: workspaceRoot,
|
||||
BuildSourceRoot: envOrDefault("RUN_BUILD_SOURCE_ROOT", "."),
|
||||
SpoolRoot: envOrDefault("RUN_SPOOL_ROOT", filepath.Join(workspaceRoot, "spool")),
|
||||
MaxJobs: intEnvOrDefault("RUN_MAX_JOBS", 1),
|
||||
HeartbeatInterval: durationEnvOrDefault("RUN_HEARTBEAT_INTERVAL_MS", 15*time.Second),
|
||||
PollInterval: durationEnvOrDefault("RUN_POLL_INTERVAL_MS", 2*time.Second),
|
||||
RetryBackoff: durationEnvOrDefault("RUN_RETRY_BACKOFF_MS", time.Second),
|
||||
UpdateJobID: os.Getenv("RUN_UPDATE_JOB_ID"),
|
||||
UpdateOutcome: os.Getenv("RUN_UPDATE_OUTCOME"),
|
||||
UpdateAttempt: intEnvOrDefault("RUN_UPDATE_ATTEMPT", 0),
|
||||
UpdateLeaseToken: os.Getenv("RUN_UPDATE_LEASE_TOKEN"),
|
||||
UpdateHealthFile: os.Getenv("RUN_UPDATE_HEALTH_FILE"),
|
||||
LocalStartupDiagnostics: os.Getenv("RUN_LOCAL_STARTUP_DIAGNOSTICS") == "1",
|
||||
}
|
||||
}
|
||||
|
||||
func stringOrDefault(value string, fallback string) string {
|
||||
if value == "" {
|
||||
return fallback
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func intStringOrDefault(value string, fallback int) int {
|
||||
parsed, err := strconv.Atoi(value)
|
||||
if err != nil || parsed <= 0 {
|
||||
return fallback
|
||||
}
|
||||
return parsed
|
||||
}
|
||||
|
||||
func envOrDefault(key string, fallback string) string {
|
||||
value := os.Getenv(key)
|
||||
if value == "" {
|
||||
return fallback
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func intEnvOrDefault(key string, fallback int) int {
|
||||
value, err := strconv.Atoi(os.Getenv(key))
|
||||
if err != nil || value <= 0 {
|
||||
return fallback
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func durationEnvOrDefault(key string, fallback time.Duration) time.Duration {
|
||||
value, err := strconv.Atoi(os.Getenv(key))
|
||||
if err != nil || value <= 0 {
|
||||
return fallback
|
||||
}
|
||||
return time.Duration(value) * time.Millisecond
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package config
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestLoadUsesDefaults(t *testing.T) {
|
||||
t.Setenv("RUN_MODE", "")
|
||||
t.Setenv("RUN_PLATFORM_URL", "")
|
||||
|
||||
cfg := Load()
|
||||
if cfg.Mode != DefaultMode {
|
||||
t.Fatalf("expected mode %q, got %q", DefaultMode, cfg.Mode)
|
||||
}
|
||||
if cfg.PlatformURL != DefaultPlatformURL {
|
||||
t.Fatalf("expected platform URL %q, got %q", DefaultPlatformURL, cfg.PlatformURL)
|
||||
}
|
||||
if cfg.RunEndpointID != DefaultEndpointID || cfg.DisplayName != DefaultDisplayName || cfg.Version != DefaultVersion {
|
||||
t.Fatalf("expected worker defaults, got %+v", cfg)
|
||||
}
|
||||
if cfg.MaxJobs != 1 || cfg.HeartbeatInterval <= 0 || cfg.PollInterval <= 0 || cfg.RetryBackoff <= 0 {
|
||||
t.Fatalf("expected positive worker scheduling defaults, got %+v", cfg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadUsesEnvironment(t *testing.T) {
|
||||
t.Setenv("RUN_MODE", "worker")
|
||||
t.Setenv("RUN_PLATFORM_URL", "http://platform.test")
|
||||
t.Setenv("RUN_ENDPOINT_ID", "run-edge")
|
||||
t.Setenv("RUN_DISPLAY_NAME", "Edge Run")
|
||||
t.Setenv("RUN_VERSION", "1.2.3")
|
||||
t.Setenv("RUN_REGISTRATION_TOKEN", "registration-token")
|
||||
t.Setenv("RUN_WORKSPACE_ROOT", "/tmp/run-workspace")
|
||||
t.Setenv("RUN_SPOOL_ROOT", "/tmp/run-spool")
|
||||
t.Setenv("RUN_MAX_JOBS", "3")
|
||||
t.Setenv("RUN_HEARTBEAT_INTERVAL_MS", "250")
|
||||
t.Setenv("RUN_POLL_INTERVAL_MS", "125")
|
||||
t.Setenv("RUN_RETRY_BACKOFF_MS", "75")
|
||||
|
||||
cfg := Load()
|
||||
if cfg.Mode != "worker" {
|
||||
t.Fatalf("expected configured mode, got %q", cfg.Mode)
|
||||
}
|
||||
if cfg.PlatformURL != "http://platform.test" {
|
||||
t.Fatalf("expected configured platform URL, got %q", cfg.PlatformURL)
|
||||
}
|
||||
if cfg.RunEndpointID != "run-edge" || cfg.DisplayName != "Edge Run" || cfg.Version != "1.2.3" || cfg.RegistrationToken != "registration-token" {
|
||||
t.Fatalf("expected configured worker identity, got %+v", cfg)
|
||||
}
|
||||
if cfg.WorkspaceRoot != "/tmp/run-workspace" || cfg.SpoolRoot != "/tmp/run-spool" || cfg.MaxJobs != 3 {
|
||||
t.Fatalf("expected configured worker paths/capacity, got %+v", cfg)
|
||||
}
|
||||
if cfg.HeartbeatInterval.Milliseconds() != 250 || cfg.PollInterval.Milliseconds() != 125 || cfg.RetryBackoff.Milliseconds() != 75 {
|
||||
t.Fatalf("expected configured durations, got %+v", cfg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadUsesBuildDefaultsWithEnvironmentOverride(t *testing.T) {
|
||||
oldMode, oldPlatformURL, oldRunEndpointID, oldDisplayName := BuildMode, BuildPlatformURL, BuildRunEndpointID, BuildDisplayName
|
||||
oldRegistrationToken, oldServerInstanceID, oldPluginID := BuildRegistrationToken, BuildServerInstanceID, BuildPluginID
|
||||
oldComponentKind, oldComponentKey, oldKeyGeneration, oldVersion, oldWorkspaceSeed := BuildComponentKind, BuildComponentKey, BuildKeyGeneration, BuildVersion, BuildWorkspaceSeed
|
||||
defer func() {
|
||||
BuildMode, BuildPlatformURL, BuildRunEndpointID, BuildDisplayName = oldMode, oldPlatformURL, oldRunEndpointID, oldDisplayName
|
||||
BuildRegistrationToken, BuildServerInstanceID, BuildPluginID = oldRegistrationToken, oldServerInstanceID, oldPluginID
|
||||
BuildComponentKind, BuildComponentKey, BuildKeyGeneration, BuildVersion = oldComponentKind, oldComponentKey, oldKeyGeneration, oldVersion
|
||||
BuildWorkspaceSeed = oldWorkspaceSeed
|
||||
}()
|
||||
|
||||
BuildMode = "worker"
|
||||
BuildPlatformURL = "https://scum.npc0.com"
|
||||
BuildRunEndpointID = "run-server-1"
|
||||
BuildDisplayName = "Run-server-1"
|
||||
BuildRegistrationToken = "compiled-run-key"
|
||||
BuildServerInstanceID = "server-1"
|
||||
BuildPluginID = "game.scum"
|
||||
BuildComponentKind = "run"
|
||||
BuildKeyGeneration = "5"
|
||||
BuildVersion = "run-dist-1"
|
||||
BuildWorkspaceSeed = "seed-payload"
|
||||
|
||||
for _, key := range []string{"RUN_MODE", "RUN_PLATFORM_URL", "RUN_ENDPOINT_ID", "RUN_DISPLAY_NAME", "RUN_REGISTRATION_TOKEN", "RUN_SERVER_INSTANCE_ID", "RUN_PLUGIN_ID", "RUN_COMPONENT_KIND", "RUN_COMPONENT_KEY", "RUN_KEY_GENERATION", "RUN_VERSION", "RUN_WORKSPACE_SEED"} {
|
||||
t.Setenv(key, "")
|
||||
}
|
||||
cfg := Load()
|
||||
if cfg.Mode != "worker" || cfg.PlatformURL != "https://scum.npc0.com" || cfg.RunEndpointID != "run-server-1" || cfg.RegistrationToken != "compiled-run-key" || cfg.ServerInstanceID != "server-1" || cfg.PluginID != "game.scum" || cfg.ComponentKind != "run" || cfg.KeyGeneration != 5 || cfg.Version != "run-dist-1" || cfg.WorkspaceSeed != "seed-payload" {
|
||||
t.Fatalf("expected compiled defaults, got %+v", cfg)
|
||||
}
|
||||
|
||||
t.Setenv("RUN_PLATFORM_URL", "http://127.0.0.1:18080")
|
||||
t.Setenv("RUN_KEY_GENERATION", "7")
|
||||
cfg = Load()
|
||||
if cfg.PlatformURL != "http://127.0.0.1:18080" || cfg.KeyGeneration != 7 {
|
||||
t.Fatalf("expected environment override, got %+v", cfg)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
PackageComponentRun = "run"
|
||||
PackageComponentClientManager = "client-manager"
|
||||
|
||||
PackageConfigEnv = "RUN_PACKAGE_CONFIG"
|
||||
)
|
||||
|
||||
type PackageConfig struct {
|
||||
Kind string `json:"kind"`
|
||||
ServerInstanceID string `json:"serverInstanceId"`
|
||||
PluginID string `json:"pluginId"`
|
||||
RunEndpointID string `json:"runEndpointId,omitempty"`
|
||||
ProfileKey string `json:"profileKey,omitempty"`
|
||||
TargetOS string `json:"targetOs"`
|
||||
TargetArch string `json:"targetArch"`
|
||||
SecretRef string `json:"secretRef"`
|
||||
KeyGeneration int `json:"keyGeneration"`
|
||||
AuthKey string `json:"authKey"`
|
||||
}
|
||||
|
||||
type PackageIdentity struct {
|
||||
Kind string `json:"kind"`
|
||||
ServerInstanceID string `json:"serverInstanceId"`
|
||||
PluginID string `json:"pluginId"`
|
||||
RunEndpointID string `json:"runEndpointId,omitempty"`
|
||||
ProfileKey string `json:"profileKey,omitempty"`
|
||||
TargetOS string `json:"targetOs"`
|
||||
TargetArch string `json:"targetArch"`
|
||||
SecretRef string `json:"secretRef"`
|
||||
KeyGeneration int `json:"keyGeneration"`
|
||||
KeyFingerprint string `json:"keyFingerprint"`
|
||||
}
|
||||
|
||||
type ComponentAuthResult struct {
|
||||
ServerInstanceID string
|
||||
Kind string
|
||||
ProfileKey string
|
||||
KeyGeneration int
|
||||
Allowed bool
|
||||
Reason string
|
||||
}
|
||||
|
||||
func LoadPackageConfig(path string) (PackageConfig, error) {
|
||||
body, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return PackageConfig{}, fmt.Errorf("read run package config: %w", err)
|
||||
}
|
||||
var cfg PackageConfig
|
||||
if err := json.Unmarshal(body, &cfg); err != nil {
|
||||
return PackageConfig{}, fmt.Errorf("decode run package config: %w", err)
|
||||
}
|
||||
if err := ValidatePackageConfig(cfg); err != nil {
|
||||
return PackageConfig{}, err
|
||||
}
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func LoadPackageConfigFromEnv() (PackageConfig, bool, error) {
|
||||
path := strings.TrimSpace(os.Getenv(PackageConfigEnv))
|
||||
if path == "" {
|
||||
return PackageConfig{}, false, nil
|
||||
}
|
||||
cfg, err := LoadPackageConfig(path)
|
||||
return cfg, true, err
|
||||
}
|
||||
|
||||
func LoadPackageConfigBesideExecutable() (PackageConfig, bool, error) {
|
||||
executable, err := os.Executable()
|
||||
if err != nil {
|
||||
return PackageConfig{}, false, nil
|
||||
}
|
||||
path := filepath.Join(filepath.Dir(executable), "config.json")
|
||||
if _, err := os.Stat(path); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return PackageConfig{}, false, nil
|
||||
}
|
||||
return PackageConfig{}, false, err
|
||||
}
|
||||
cfg, err := LoadPackageConfig(path)
|
||||
return cfg, true, err
|
||||
}
|
||||
|
||||
func ValidatePackageConfig(cfg PackageConfig) error {
|
||||
var violations []string
|
||||
if cfg.Kind != PackageComponentRun && cfg.Kind != PackageComponentClientManager {
|
||||
violations = append(violations, "kind is invalid")
|
||||
}
|
||||
if !safeIdentifier(cfg.ServerInstanceID) {
|
||||
violations = append(violations, "serverInstanceId is invalid")
|
||||
}
|
||||
if !safePluginID(cfg.PluginID) {
|
||||
violations = append(violations, "pluginId is invalid")
|
||||
}
|
||||
if cfg.RunEndpointID != "" && !safeIdentifier(cfg.RunEndpointID) {
|
||||
violations = append(violations, "runEndpointId is invalid")
|
||||
}
|
||||
if cfg.ProfileKey != "" && !safeLogicalKey(cfg.ProfileKey) {
|
||||
violations = append(violations, "profileKey is invalid")
|
||||
}
|
||||
if cfg.Kind == PackageComponentClientManager && cfg.ProfileKey == "" {
|
||||
violations = append(violations, "profileKey is required for client-manager packages")
|
||||
}
|
||||
if !safeRuntimeTarget(cfg.TargetOS, cfg.TargetArch) {
|
||||
violations = append(violations, "target platform is invalid")
|
||||
}
|
||||
if !strings.HasPrefix(cfg.SecretRef, "secret://runtime-keys/") || containsUnsafeDiagnosticText(cfg.SecretRef) {
|
||||
violations = append(violations, "secretRef is invalid")
|
||||
}
|
||||
if cfg.KeyGeneration <= 0 {
|
||||
violations = append(violations, "keyGeneration must be positive")
|
||||
}
|
||||
if strings.TrimSpace(cfg.AuthKey) == "" {
|
||||
violations = append(violations, "authKey is required")
|
||||
}
|
||||
if containsUnsafeDiagnosticText(cfg.AuthKey) {
|
||||
violations = append(violations, "authKey contains unsafe content")
|
||||
}
|
||||
if len(violations) > 0 {
|
||||
return fmt.Errorf("invalid run package config: %s", strings.Join(violations, "; "))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ApplyPackageConfig(base Config, pkg PackageConfig) Config {
|
||||
base.RegistrationToken = pkg.AuthKey
|
||||
base.ServerInstanceID = pkg.ServerInstanceID
|
||||
base.PluginID = pkg.PluginID
|
||||
base.ComponentKind = pkg.Kind
|
||||
base.ComponentKey = pkg.ProfileKey
|
||||
base.KeyGeneration = pkg.KeyGeneration
|
||||
base.SecretRef = pkg.SecretRef
|
||||
if pkg.RunEndpointID != "" {
|
||||
base.RunEndpointID = pkg.RunEndpointID
|
||||
}
|
||||
if base.DisplayName == "" || base.DisplayName == DefaultDisplayName {
|
||||
base.DisplayName = "Run " + pkg.ServerInstanceID
|
||||
}
|
||||
return base
|
||||
}
|
||||
|
||||
func (cfg PackageConfig) Identity() PackageIdentity {
|
||||
return PackageIdentity{
|
||||
Kind: cfg.Kind,
|
||||
ServerInstanceID: cfg.ServerInstanceID,
|
||||
PluginID: cfg.PluginID,
|
||||
RunEndpointID: cfg.RunEndpointID,
|
||||
ProfileKey: cfg.ProfileKey,
|
||||
TargetOS: cfg.TargetOS,
|
||||
TargetArch: cfg.TargetArch,
|
||||
SecretRef: cfg.SecretRef,
|
||||
KeyGeneration: cfg.KeyGeneration,
|
||||
KeyFingerprint: fingerprint(cfg.AuthKey),
|
||||
}
|
||||
}
|
||||
|
||||
func (cfg PackageConfig) RedactedDiagnostics() map[string]string {
|
||||
identity := cfg.Identity()
|
||||
return map[string]string{
|
||||
"kind": identity.Kind,
|
||||
"serverInstanceId": identity.ServerInstanceID,
|
||||
"pluginId": identity.PluginID,
|
||||
"runEndpointId": identity.RunEndpointID,
|
||||
"profileKey": identity.ProfileKey,
|
||||
"target": identity.TargetOS + "/" + identity.TargetArch,
|
||||
"secretRef": identity.SecretRef,
|
||||
"keyGeneration": fmt.Sprintf("%d", identity.KeyGeneration),
|
||||
"keyFingerprint": identity.KeyFingerprint,
|
||||
}
|
||||
}
|
||||
|
||||
func AuthenticatePackageGeneration(pkg PackageConfig, auth ComponentAuthResult) error {
|
||||
if err := ValidatePackageConfig(pkg); err != nil {
|
||||
return err
|
||||
}
|
||||
if auth.ServerInstanceID != pkg.ServerInstanceID || auth.Kind != pkg.Kind || auth.ProfileKey != pkg.ProfileKey {
|
||||
return fmt.Errorf("component authentication scope does not match package")
|
||||
}
|
||||
if !auth.Allowed {
|
||||
return fmt.Errorf("component authentication rejected: %s", redactedReason(auth.Reason))
|
||||
}
|
||||
if auth.KeyGeneration != pkg.KeyGeneration {
|
||||
return fmt.Errorf("component key generation is no longer current")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func fingerprint(value string) string {
|
||||
sum := sha256.Sum256([]byte(value))
|
||||
return hex.EncodeToString(sum[:])[:12]
|
||||
}
|
||||
|
||||
func safeIdentifier(value string) bool {
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" || len(value) > 120 || containsUnsafeDiagnosticText(value) {
|
||||
return false
|
||||
}
|
||||
for _, char := range value {
|
||||
if (char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z') || (char >= '0' && char <= '9') || char == '_' || char == '-' || char == '.' {
|
||||
continue
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func safePluginID(value string) bool {
|
||||
return strings.HasPrefix(value, "game.") && safeIdentifier(value)
|
||||
}
|
||||
|
||||
func safeLogicalKey(value string) bool {
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" || len(value) > 120 || strings.HasPrefix(value, "/") || strings.Contains(value, "..") || strings.Contains(value, `\`) || containsUnsafeDiagnosticText(value) {
|
||||
return false
|
||||
}
|
||||
for _, char := range value {
|
||||
if (char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z') || (char >= '0' && char <= '9') || char == '_' || char == '-' || char == '.' || char == '/' {
|
||||
continue
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func safeRuntimeTarget(osName string, arch string) bool {
|
||||
switch osName {
|
||||
case "windows", "linux", "darwin":
|
||||
default:
|
||||
return false
|
||||
}
|
||||
switch arch {
|
||||
case "amd64", "arm64":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func containsUnsafeDiagnosticText(value string) bool {
|
||||
normalized := strings.ToLower(value)
|
||||
for _, marker := range []string{"/users/", "/.ssh/", "password=", "apikey", "api_key", "bearer ", "sk-", "unix://", "tcp://", "mysql://", "sqlite://"} {
|
||||
if strings.Contains(normalized, marker) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func redactedReason(value string) string {
|
||||
if containsUnsafeDiagnosticText(value) {
|
||||
return "[redacted]"
|
||||
}
|
||||
return value
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLoadPackageConfigAppliesServerScopedIdentity(t *testing.T) {
|
||||
path := writePackageConfig(t, PackageConfig{
|
||||
Kind: PackageComponentRun,
|
||||
ServerInstanceID: "server-1",
|
||||
PluginID: "game.minecraft",
|
||||
RunEndpointID: "run-server-1",
|
||||
TargetOS: "linux",
|
||||
TargetArch: "amd64",
|
||||
SecretRef: "secret://runtime-keys/server-1/run/current",
|
||||
KeyGeneration: 3,
|
||||
AuthKey: "opaque-runtime-key",
|
||||
})
|
||||
|
||||
pkg, err := LoadPackageConfig(path)
|
||||
if err != nil {
|
||||
t.Fatalf("load package config: %v", err)
|
||||
}
|
||||
cfg := ApplyPackageConfig(Config{RunEndpointID: DefaultEndpointID, DisplayName: DefaultDisplayName}, pkg)
|
||||
if cfg.RegistrationToken != "opaque-runtime-key" || cfg.RunEndpointID != "run-server-1" || cfg.ServerInstanceID != "server-1" || cfg.KeyGeneration != 3 {
|
||||
t.Fatalf("expected package identity to be applied, got %+v", cfg)
|
||||
}
|
||||
diagnostics := pkg.RedactedDiagnostics()
|
||||
for _, value := range diagnostics {
|
||||
if strings.Contains(value, "opaque-runtime-key") || strings.Contains(value, "/Users/") || strings.Contains(value, "password=") {
|
||||
t.Fatalf("diagnostics exposed sensitive value: %+v", diagnostics)
|
||||
}
|
||||
}
|
||||
if diagnostics["keyFingerprint"] == "" || diagnostics["secretRef"] != "secret://runtime-keys/server-1/run/current" {
|
||||
t.Fatalf("expected redacted key fingerprint and secret ref, got %+v", diagnostics)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadPackageConfigRejectsUnsafeOrIncompletePackages(t *testing.T) {
|
||||
valid := PackageConfig{
|
||||
Kind: PackageComponentRun,
|
||||
ServerInstanceID: "server-1",
|
||||
PluginID: "game.scum",
|
||||
TargetOS: "windows",
|
||||
TargetArch: "amd64",
|
||||
SecretRef: "secret://runtime-keys/server-1/run/current",
|
||||
KeyGeneration: 1,
|
||||
AuthKey: "opaque-runtime-key",
|
||||
}
|
||||
cases := map[string]func(PackageConfig) PackageConfig{
|
||||
"old zero generation": func(cfg PackageConfig) PackageConfig { cfg.KeyGeneration = 0; return cfg },
|
||||
"raw path": func(cfg PackageConfig) PackageConfig { cfg.ServerInstanceID = "/Users/tasia/server"; return cfg },
|
||||
"socket": func(cfg PackageConfig) PackageConfig { cfg.SecretRef = "unix:///tmp/run.sock"; return cfg },
|
||||
"secret auth": func(cfg PackageConfig) PackageConfig { cfg.AuthKey = "password=raw"; return cfg },
|
||||
"client missing key": func(cfg PackageConfig) PackageConfig { cfg.Kind = PackageComponentClientManager; return cfg },
|
||||
}
|
||||
for name, mutate := range cases {
|
||||
if err := ValidatePackageConfig(mutate(valid)); err == nil {
|
||||
t.Fatalf("expected %s package to be rejected", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthenticatePackageGenerationRejectsStalePackages(t *testing.T) {
|
||||
pkg := PackageConfig{
|
||||
Kind: PackageComponentRun,
|
||||
ServerInstanceID: "server-1",
|
||||
PluginID: "game.minecraft",
|
||||
TargetOS: "linux",
|
||||
TargetArch: "amd64",
|
||||
SecretRef: "secret://runtime-keys/server-1/run/current",
|
||||
KeyGeneration: 1,
|
||||
AuthKey: "opaque-runtime-key",
|
||||
}
|
||||
err := AuthenticatePackageGeneration(pkg, ComponentAuthResult{
|
||||
ServerInstanceID: "server-1",
|
||||
Kind: PackageComponentRun,
|
||||
KeyGeneration: 2,
|
||||
Allowed: true,
|
||||
Reason: "current key accepted",
|
||||
})
|
||||
if err == nil || !strings.Contains(err.Error(), "generation") {
|
||||
t.Fatalf("expected stale generation rejection, got %v", err)
|
||||
}
|
||||
err = AuthenticatePackageGeneration(pkg, ComponentAuthResult{
|
||||
ServerInstanceID: "server-1",
|
||||
Kind: PackageComponentRun,
|
||||
KeyGeneration: 1,
|
||||
Allowed: true,
|
||||
Reason: "current key accepted",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("expected current generation to authenticate: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadPackageConfigFromEnv(t *testing.T) {
|
||||
path := writePackageConfig(t, PackageConfig{
|
||||
Kind: PackageComponentClientManager,
|
||||
ServerInstanceID: "server-1",
|
||||
PluginID: "game.scum",
|
||||
ProfileKey: "scum-client-manager",
|
||||
TargetOS: "windows",
|
||||
TargetArch: "amd64",
|
||||
SecretRef: "secret://runtime-keys/server-1/client-manager/scum-client-manager/current",
|
||||
KeyGeneration: 4,
|
||||
AuthKey: "opaque-client-key",
|
||||
})
|
||||
t.Setenv(PackageConfigEnv, path)
|
||||
|
||||
cfg, ok, err := LoadPackageConfigFromEnv()
|
||||
if err != nil || !ok {
|
||||
t.Fatalf("expected env package config, ok=%v err=%v", ok, err)
|
||||
}
|
||||
if cfg.Kind != PackageComponentClientManager || cfg.ProfileKey != "scum-client-manager" {
|
||||
t.Fatalf("unexpected package config: %+v", cfg)
|
||||
}
|
||||
|
||||
t.Setenv(PackageConfigEnv, "")
|
||||
_, ok, err = LoadPackageConfigFromEnv()
|
||||
if err != nil || ok {
|
||||
t.Fatalf("expected no env package config, ok=%v err=%v", ok, err)
|
||||
}
|
||||
}
|
||||
|
||||
func writePackageConfig(t *testing.T, cfg PackageConfig) string {
|
||||
t.Helper()
|
||||
body, err := json.Marshal(cfg)
|
||||
if err != nil {
|
||||
t.Fatalf("marshal package config: %v", err)
|
||||
}
|
||||
path := filepath.Join(t.TempDir(), "run-package.json")
|
||||
if err := os.WriteFile(path, body, 0o600); err != nil {
|
||||
t.Fatalf("write package config: %v", err)
|
||||
}
|
||||
return path
|
||||
}
|
||||
Reference in New Issue
Block a user