功能修改
This commit is contained in:
@@ -0,0 +1,166 @@
|
||||
package companion
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
const (
|
||||
ConfigSchemaVersion = 1
|
||||
PluginID = "game.scum"
|
||||
ProfileKey = "scum-client-manager"
|
||||
ProofEnvironment = "SCUM_COMPONENT_PROOF"
|
||||
)
|
||||
|
||||
var requiredCapabilities = []string{
|
||||
"component.register",
|
||||
"component.heartbeat",
|
||||
"component.health",
|
||||
"component.control",
|
||||
"game-client.bridge",
|
||||
"logs.stream",
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
SchemaVersion int `json:"schemaVersion" yaml:"schemaVersion"`
|
||||
Platform PlatformConfig `json:"platform" yaml:"platform"`
|
||||
Component ComponentConfig `json:"component" yaml:"component"`
|
||||
Proof ProofConfig `json:"proof" yaml:"proof"`
|
||||
Session SessionConfig `json:"session" yaml:"session"`
|
||||
Capabilities []string `json:"capabilities" yaml:"capabilities"`
|
||||
Timing TimingConfig `json:"timing" yaml:"timing"`
|
||||
TLS TransportTLSConfig `json:"tls" yaml:"tls"`
|
||||
}
|
||||
|
||||
type PlatformConfig struct {
|
||||
BaseURL string `json:"baseUrl" yaml:"baseUrl"`
|
||||
}
|
||||
|
||||
type ComponentConfig struct {
|
||||
InstallationID string `json:"installationId" yaml:"installationId"`
|
||||
ServerInstanceID string `json:"serverInstanceId" yaml:"serverInstanceId"`
|
||||
PluginID string `json:"pluginId" yaml:"pluginId"`
|
||||
ProfileKey string `json:"profileKey" yaml:"profileKey"`
|
||||
ArtifactID string `json:"artifactId" yaml:"artifactId"`
|
||||
Version string `json:"version" yaml:"version"`
|
||||
SourceRevision string `json:"sourceRevision" yaml:"sourceRevision"`
|
||||
TargetOS string `json:"targetOs" yaml:"targetOs"`
|
||||
TargetArch string `json:"targetArch" yaml:"targetArch"`
|
||||
KeyGeneration int `json:"keyGeneration" yaml:"keyGeneration"`
|
||||
DeploymentGeneration int `json:"deploymentGeneration" yaml:"deploymentGeneration"`
|
||||
}
|
||||
|
||||
type ProofConfig struct {
|
||||
Mode string `json:"mode" yaml:"mode"`
|
||||
MaterialEnv string `json:"materialEnv" yaml:"materialEnv"`
|
||||
}
|
||||
|
||||
type SessionConfig struct {
|
||||
Mode string `json:"mode" yaml:"mode"`
|
||||
}
|
||||
|
||||
type TimingConfig struct {
|
||||
HeartbeatIntervalSeconds int `json:"heartbeatIntervalSeconds" yaml:"heartbeatIntervalSeconds"`
|
||||
CommandPollIntervalSeconds int `json:"commandPollIntervalSeconds" yaml:"commandPollIntervalSeconds"`
|
||||
RequestTimeoutSeconds int `json:"requestTimeoutSeconds" yaml:"requestTimeoutSeconds"`
|
||||
}
|
||||
|
||||
type TransportTLSConfig struct {
|
||||
Policy string `json:"policy" yaml:"policy"`
|
||||
}
|
||||
|
||||
func LoadConfig(reader io.Reader) (Config, error) {
|
||||
decoder := yaml.NewDecoder(reader)
|
||||
decoder.KnownFields(true)
|
||||
var config Config
|
||||
if err := decoder.Decode(&config); err != nil {
|
||||
return Config{}, fmt.Errorf("decode companion config: %w", err)
|
||||
}
|
||||
var trailing any
|
||||
if err := decoder.Decode(&trailing); err != io.EOF {
|
||||
if err == nil {
|
||||
return Config{}, fmt.Errorf("decode companion config: multiple documents are not allowed")
|
||||
}
|
||||
return Config{}, fmt.Errorf("decode companion config: %w", err)
|
||||
}
|
||||
if err := config.Validate(); err != nil {
|
||||
return Config{}, err
|
||||
}
|
||||
config.Platform.BaseURL, _ = canonicalPlatformOrigin(config.Platform.BaseURL)
|
||||
config.Capabilities = append([]string(nil), config.Capabilities...)
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func (config Config) Validate() error {
|
||||
if config.SchemaVersion != ConfigSchemaVersion {
|
||||
return fmt.Errorf("companion config schema version is unsupported")
|
||||
}
|
||||
if _, err := canonicalPlatformOrigin(config.Platform.BaseURL); err != nil {
|
||||
return err
|
||||
}
|
||||
component := config.Component
|
||||
if component.InstallationID == "" || component.ServerInstanceID == "" || component.ArtifactID == "" || component.Version == "" || component.SourceRevision == "" {
|
||||
return fmt.Errorf("component identity is incomplete")
|
||||
}
|
||||
if component.PluginID != PluginID || component.ProfileKey != ProfileKey || component.TargetOS != "windows" || component.TargetArch != "amd64" {
|
||||
return fmt.Errorf("component identity does not match the SCUM companion profile")
|
||||
}
|
||||
if component.KeyGeneration <= 0 || component.DeploymentGeneration <= 0 {
|
||||
return fmt.Errorf("component generations must be positive")
|
||||
}
|
||||
if config.Proof.Mode != "hmac-sha256" || config.Proof.MaterialEnv != ProofEnvironment {
|
||||
return fmt.Errorf("component proof policy is unsupported")
|
||||
}
|
||||
if config.Session.Mode != "component-session" {
|
||||
return fmt.Errorf("component session policy is unsupported")
|
||||
}
|
||||
if config.TLS.Policy != "verify-system-roots" {
|
||||
return fmt.Errorf("TLS policy must verify system roots")
|
||||
}
|
||||
if err := validateCapabilities(config.Capabilities); err != nil {
|
||||
return err
|
||||
}
|
||||
if config.Timing.HeartbeatIntervalSeconds < 5 || config.Timing.HeartbeatIntervalSeconds > 300 || config.Timing.CommandPollIntervalSeconds < 1 || config.Timing.CommandPollIntervalSeconds > 60 || config.Timing.RequestTimeoutSeconds < 1 || config.Timing.RequestTimeoutSeconds > 60 {
|
||||
return fmt.Errorf("companion timing policy is invalid")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func canonicalPlatformOrigin(value string) (string, error) {
|
||||
parsed, err := url.Parse(strings.TrimSpace(value))
|
||||
if err != nil || parsed.Scheme != "https" || parsed.Host == "" || parsed.Hostname() == "" || parsed.User != nil || parsed.RawQuery != "" || parsed.Fragment != "" || parsed.Path != "" && parsed.Path != "/" {
|
||||
return "", fmt.Errorf("platform base URL must be a credential-free HTTPS origin")
|
||||
}
|
||||
port := parsed.Port()
|
||||
if strings.HasSuffix(parsed.Host, ":") || port != "" {
|
||||
value, err := strconv.Atoi(port)
|
||||
if err != nil || value < 1 || value > 65535 {
|
||||
return "", fmt.Errorf("platform base URL must use a valid HTTPS port")
|
||||
}
|
||||
}
|
||||
return (&url.URL{Scheme: parsed.Scheme, Host: parsed.Host}).String(), nil
|
||||
}
|
||||
|
||||
func validateCapabilities(capabilities []string) error {
|
||||
if len(capabilities) != len(requiredCapabilities) {
|
||||
return fmt.Errorf("component capabilities do not match the SCUM companion profile")
|
||||
}
|
||||
actual := make(map[string]struct{}, len(capabilities))
|
||||
for _, capability := range capabilities {
|
||||
if _, exists := actual[capability]; exists {
|
||||
return fmt.Errorf("component capabilities must be unique")
|
||||
}
|
||||
actual[capability] = struct{}{}
|
||||
}
|
||||
for _, capability := range requiredCapabilities {
|
||||
if _, exists := actual[capability]; !exists {
|
||||
return fmt.Errorf("component capabilities do not match the SCUM companion profile")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user