Move SCUM lifecycle ownership to plugin

This commit is contained in:
npc0-hue
2026-08-01 09:36:12 +08:00
parent bca4f935ba
commit d1249fca85
49 changed files with 795 additions and 566 deletions
+85 -1
View File
@@ -159,6 +159,7 @@ func ValidateGamePlugin(plugin domain.GamePlugin) error {
violations = append(violations, validateGameClientBridgeManifest("gameClientBridge", plugin.GameClientBridge, plugin.DeclaredPermissions, plugin.Pages, plugin.RuntimeProfiles)...)
violations = append(violations, validateMapTrajectoryDeclaration("mapTrajectories", plugin.MapTrajectories)...)
violations = append(violations, validatePluginCreateFields("createFields", plugin.CreateFields)...)
violations = append(violations, validatePluginAssetFiles("lifecycleAssets", plugin.LifecycleAssets)...)
violations = append(violations, validateSafePluginStrings("gamePlugin", pluginSafeStrings(plugin))...)
return finish(violations)
}
@@ -229,10 +230,87 @@ func ValidateGamePluginManifestRegistration(registration domain.GamePluginManife
violations = append(violations, validateRuntimeLogEventPermissionDeclarations("manifest.runtimeProfiles.logEvents", manifest.RuntimeProfiles, manifest.Permissions)...)
violations = append(violations, validateGameClientBridgeManifest("manifest.gameClientBridge", manifest.GameClientBridge, manifest.Permissions, manifest.Pages, manifest.RuntimeProfiles)...)
violations = append(violations, validateMapTrajectoryDeclaration("manifest.mapTrajectories", manifest.MapTrajectories)...)
violations = append(violations, validatePluginAssetFileDeclarations("manifest.assetFiles", manifest.AssetFiles)...)
violations = append(violations, validatePluginAssetFiles("assetFiles", registration.AssetFiles)...)
violations = append(violations, validateRegistrationAssetCoverage(registration.Manifest.AssetFiles, registration.AssetFiles)...)
violations = append(violations, validateSafePluginStrings("manifest", manifestSafeStrings(registration))...)
return finish(violations)
}
func validatePluginAssetFileDeclarations(prefix string, files []domain.PluginAssetFile) []string {
if len(files) > 64 {
return []string{prefix + " has too many files"}
}
var violations []string
seen := map[string]struct{}{}
for i, file := range files {
field := fmt.Sprintf("%s[%d]", prefix, i)
if !validLogicalFileKey(file.Path) {
violations = append(violations, field+".path is unsafe")
}
if _, exists := seen[file.Path]; exists {
violations = append(violations, field+".path is duplicated")
}
seen[file.Path] = struct{}{}
if file.Content != "" {
violations = append(violations, field+".content must be supplied only in registration assetFiles")
}
if file.Mode != 0 && file.Mode != 0o600 && file.Mode != 0o700 {
violations = append(violations, field+".mode is unsafe")
}
}
return violations
}
func validatePluginAssetFiles(prefix string, files []domain.PluginAssetFile) []string {
if len(files) > 64 {
return []string{prefix + " has too many files"}
}
var violations []string
seen := map[string]struct{}{}
for i, file := range files {
field := fmt.Sprintf("%s[%d]", prefix, i)
if !validLogicalFileKey(file.Path) {
violations = append(violations, field+".path is unsafe")
}
if _, exists := seen[file.Path]; exists {
violations = append(violations, field+".path is duplicated")
}
seen[file.Path] = struct{}{}
if len([]byte(file.Content)) > 64*1024 || strings.ContainsRune(file.Content, '\x00') || containsUnsafeRuntimeSecret(file.Content) {
violations = append(violations, field+".content is unsafe")
}
if file.Mode != 0 && (file.Mode < 0o400 || file.Mode > 0o700 || file.Mode&0o022 != 0) {
violations = append(violations, field+".mode is unsafe")
}
}
return violations
}
func validateRegistrationAssetCoverage(declared []domain.PluginAssetFile, payload []domain.PluginAssetFile) []string {
if len(declared) == 0 {
return nil
}
allowed := map[string]struct{}{}
for _, file := range declared {
allowed[file.Path] = struct{}{}
}
provided := map[string]struct{}{}
for _, file := range payload {
provided[file.Path] = struct{}{}
if _, ok := allowed[file.Path]; !ok {
return []string{"assetFiles contains undeclared plugin asset " + file.Path}
}
}
var violations []string
for _, file := range declared {
if _, ok := provided[file.Path]; !ok {
violations = append(violations, "assetFiles is missing declared plugin asset "+file.Path)
}
}
return violations
}
func validateMapTrajectoryDeclaration(prefix string, value *domain.GameMapTrajectoryDeclaration) []string {
if value == nil {
return nil
@@ -1711,6 +1789,9 @@ func pluginSafeStrings(plugin domain.GamePlugin) []fieldString {
values = appendStringSliceFields(values, "remoteAccess.methods", plugin.RemoteAccess.Methods)
values = appendStringSliceFields(values, "remoteAccess.runCapabilities", plugin.RemoteAccess.RunCapabilities)
values = appendStringSliceFields(values, "remoteAccess.databaseEngines", plugin.RemoteAccess.DatabaseEngines)
for i, file := range plugin.LifecycleAssets {
values = append(values, fieldString{field: fmt.Sprintf("lifecycleAssets[%d].path", i), value: file.Path})
}
for i, page := range plugin.Pages {
prefix := fmt.Sprintf("pages[%d]", i)
values = append(values,
@@ -1753,6 +1834,9 @@ func manifestSafeStrings(registration domain.GamePluginManifestRegistration) []f
values = appendStringSliceFields(values, "remoteAccess.methods", manifest.RemoteAccess.Methods)
values = appendStringSliceFields(values, "remoteAccess.runCapabilities", manifest.RemoteAccess.RunCapabilities)
values = appendStringSliceFields(values, "remoteAccess.databaseEngines", manifest.RemoteAccess.DatabaseEngines)
for i, file := range manifest.AssetFiles {
values = append(values, fieldString{field: fmt.Sprintf("assetFiles[%d].path", i), value: file.Path})
}
for i, page := range manifest.Pages {
prefix := fmt.Sprintf("pages[%d]", i)
values = append(values,
@@ -1953,7 +2037,7 @@ func validPluginRunCapability(capability string) bool {
domain.JobCapabilityRemoteRunLogsTransfer, domain.JobCapabilityRemoteRunRCONCommand,
domain.JobCapabilityRemoteRunProtectedSQL, domain.JobCapabilityRemoteRunProtectedRCON, domain.JobCapabilityRemoteRunProgram,
domain.JobCapabilityRunSelfUpdate, domain.JobCapabilityDependenciesCheck, domain.JobCapabilityDependenciesInstall,
domain.JobCapabilityDeploymentPlan, domain.JobCapabilitySCUMDeploymentPlan, domain.JobCapabilityDeploymentShellPosix, domain.JobCapabilityDeploymentShellPowerShell, domain.JobCapabilityDeploymentShellCmd,
domain.JobCapabilityDeploymentPlan, domain.JobCapabilityDeploymentShellPosix, domain.JobCapabilityDeploymentShellPowerShell, domain.JobCapabilityDeploymentShellCmd,
domain.JobCapabilityClientManagerDeploy, domain.JobCapabilityClientManagerControl, domain.JobCapabilityClientManagerUpdate,
domain.JobCapabilityClientManagerRollback, domain.JobCapabilityClientManagerUninstall,
"artifacts.read", "artifacts.write", "artifact.read", "artifact.write",