From dcd82635a8f063523350fee0e738886cacae868a Mon Sep 17 00:00:00 2001 From: yannic rieger Date: Sun, 22 Mar 2026 17:25:26 +0100 Subject: [PATCH 1/3] sanatize server configs before creating the image we want to avoid that users upload configs that will lead to broken servers (e.g. player not being able to join, because wrong server port etc.). so we change them before building the checkpoint. --- controlplane/serverconfig/paper.go | 52 +++++++++++ .../serverconfig/paper_internal_test.go | 46 ++++++++++ controlplane/serverconfig/sanatize.go | 60 +++++++++++++ controlplane/serverconfig/sanatize_test.go | 89 +++++++++++++++++++ controlplane/serverconfig/vanilla.go | 36 ++++++++ .../serverconfig/vanilla_internal_test.go | 43 +++++++++ controlplane/worker/create_image.go | 12 ++- 7 files changed, 335 insertions(+), 3 deletions(-) create mode 100644 controlplane/serverconfig/paper.go create mode 100644 controlplane/serverconfig/paper_internal_test.go create mode 100644 controlplane/serverconfig/sanatize.go create mode 100644 controlplane/serverconfig/sanatize_test.go create mode 100644 controlplane/serverconfig/vanilla.go create mode 100644 controlplane/serverconfig/vanilla_internal_test.go diff --git a/controlplane/serverconfig/paper.go b/controlplane/serverconfig/paper.go new file mode 100644 index 00000000..39b591da --- /dev/null +++ b/controlplane/serverconfig/paper.go @@ -0,0 +1,52 @@ +package serverconfig + +import ( + "fmt" + + "github.com/goccy/go-yaml" + "github.com/spacechunks/explorer/controlplane/blob" +) + +/* +proxies: + bungee-cord: + online-mode: true + proxy-protocol: false + velocity: + enabled: false + online-mode: true + secret: "" +*/ + +type paperGlobal struct { + Proxies proxiesConfig `json:"proxies"` +} + +type proxiesConfig struct { + ProxyProtocol bool `json:"proxy-protocol"` + Velocity struct { + Enabled bool `json:"enabled"` + OnlineMode bool `json:"online-mode"` + Secret string `json:"secret"` + } +} + +func sanatizePaperGlobal(data []byte) ([]byte, error) { + var global paperGlobal + if err := yaml.Unmarshal(data, &blob.Object{}); err != nil { + return nil, fmt.Errorf("unmarshal: %w", err) + } + + global.Proxies.ProxyProtocol = false + + global.Proxies.Velocity.Enabled = true + global.Proxies.Velocity.OnlineMode = true + global.Proxies.Velocity.Secret = "" + + ret, err := yaml.Marshal(global) + if err != nil { + return nil, fmt.Errorf("marshal: %w", err) + } + + return ret, nil +} diff --git a/controlplane/serverconfig/paper_internal_test.go b/controlplane/serverconfig/paper_internal_test.go new file mode 100644 index 00000000..7e21372c --- /dev/null +++ b/controlplane/serverconfig/paper_internal_test.go @@ -0,0 +1,46 @@ +package serverconfig + +import ( + "testing" + + "github.com/goccy/go-yaml" + "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/require" +) + +func TestPaperConfigAdjustments(t *testing.T) { + input := ` +proxies: + bungee-cord: + online-mode: true + proxy-protocol: true + velocity: + enabled: false + online-mode: false + secret: "blalala" +` + expectedCfg := paperGlobal{ + Proxies: proxiesConfig{ + ProxyProtocol: false, + Velocity: struct { + Enabled bool `json:"enabled"` + OnlineMode bool `json:"online-mode"` + Secret string `json:"secret"` + }{ + Enabled: true, + OnlineMode: true, + Secret: "", + }, + }, + } + + expectedYaml, err := yaml.Marshal(expectedCfg) + require.NoError(t, err) + + actual, err := sanatizePaperGlobal([]byte(input)) + require.NoError(t, err) + + if d := cmp.Diff(string(expectedYaml), string(actual)); d != "" { + t.Fatalf("mismatch (-want +got):\n%s", d) + } +} diff --git a/controlplane/serverconfig/sanatize.go b/controlplane/serverconfig/sanatize.go new file mode 100644 index 00000000..b1a457d2 --- /dev/null +++ b/controlplane/serverconfig/sanatize.go @@ -0,0 +1,60 @@ +package serverconfig + +import ( + "fmt" + "io/fs" + "os" +) + +type sanatize func(data []byte) ([]byte, error) + +var sanatizers map[string]sanatize + +func init() { + sanatizers = map[string]sanatize{ + "server.properties": sanatizeServerProperties, + "config/paper-global.yml": sanatizePaperGlobal, + } +} + +func SanitizeConfigs(root *os.Root) error { + if err := fs.WalkDir(root.FS(), ".", func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + + if d.IsDir() { + return nil + } + + sanatize, ok := sanatizers[path] + if !ok { + return nil + } + + data, err := root.ReadFile(path) + if err != nil { + return fmt.Errorf("failed to read file: %w", err) + } + + sanatized, err := sanatize(data) + if err != nil { + return fmt.Errorf("sanatize file %s: %w", path, err) + } + + info, err := d.Info() + if err != nil { + return fmt.Errorf("file info %s: %w", path, err) + } + + if err := root.WriteFile(path, sanatized, info.Mode()); err != nil { + return fmt.Errorf("write file %s: %w", path, err) + } + + return nil + }); err != nil { + return err + } + + return nil +} diff --git a/controlplane/serverconfig/sanatize_test.go b/controlplane/serverconfig/sanatize_test.go new file mode 100644 index 00000000..610b57d0 --- /dev/null +++ b/controlplane/serverconfig/sanatize_test.go @@ -0,0 +1,89 @@ +package serverconfig_test + +import ( + "os" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/spacechunks/explorer/controlplane/serverconfig" + "github.com/stretchr/testify/require" +) + +func TestSanatizeConfigs(t *testing.T) { + root, err := os.OpenRoot(t.TempDir()) + require.NoError(t, err) + + paperGlobal := ` +proxies: + bungee-cord: + online-mode: true + proxy-protocol: true + velocity: + enabled: false + online-mode: false + secret: "blalala" +` + + err = root.Mkdir("config", os.ModePerm) + require.NoError(t, err) + + err = root.WriteFile("config/paper-global.yml", []byte(paperGlobal), os.ModePerm) + require.NoError(t, err) + + properties := ` +#Minecraft server properties +#Mon Mar 09 17:21:18 CET 2026 +log-ips=true +management-server-allowed-origins= +management-server-enabled=false +management-server-host=1.1.1.1 +management-server-port=1337 +management-server-secret=wrong +management-server-tls-enabled=false +online-mode=true +server-ip= +server-port=1337 +use-native-transport=true +` + + err = root.WriteFile("server.properties", []byte(properties), os.ModePerm) + require.NoError(t, err) + + err = serverconfig.SanitizeConfigs(root) + require.NoError(t, err) + + expectedPaperGlobal := `proxies: + proxy-protocol: false + velocity: + enabled: true + online-mode: true + secret: "" +` + + expectedProperties := `log-ips = false +management-server-allowed-origins = * +management-server-enabled = true +management-server-host = localhost +management-server-port = 26656 +management-server-secret = change-me-later +management-server-tls-enabled = false +online-mode = false +server-ip = 0.0.0.0 +server-port = 25565 +use-native-transport = true +` + + actualPaperGlobal, err := root.ReadFile("config/paper-global.yml") + require.NoError(t, err) + + actualProperties, err := root.ReadFile("server.properties") + require.NoError(t, err) + + if d := cmp.Diff(expectedPaperGlobal, string(actualPaperGlobal)); d != "" { + t.Fatalf("mismatch (-want +got):\n%s", d) + } + + if d := cmp.Diff(expectedProperties, string(actualProperties)); d != "" { + t.Fatalf("mismatch (-want +got):\n%s", d) + } +} diff --git a/controlplane/serverconfig/vanilla.go b/controlplane/serverconfig/vanilla.go new file mode 100644 index 00000000..056a0ed6 --- /dev/null +++ b/controlplane/serverconfig/vanilla.go @@ -0,0 +1,36 @@ +package serverconfig + +import ( + "fmt" + + "github.com/magiconair/properties" +) + +func sanatizeServerProperties(data []byte) ([]byte, error) { + props, err := properties.LoadString(string(data)) + if err != nil { + return nil, fmt.Errorf("parse properties: %w", err) + } + + vals := map[string]any{ + "server-ip": "0.0.0.0", + "server-port": 25565, + "management-server-allowed-origins": "*", + "management-server-enabled": true, + "management-server-host": "localhost", + "management-server-port": 26656, + "management-server-secret": "change-me-later", + "management-server-tls-enabled": false, + "online-mode": false, + "log-ips": false, + "use-native-transport": true, + } + + for k, v := range vals { + if err := props.SetValue(k, v); err != nil { + return nil, fmt.Errorf("set %s: %w", k, err) + } + } + + return []byte(props.String()), nil +} diff --git a/controlplane/serverconfig/vanilla_internal_test.go b/controlplane/serverconfig/vanilla_internal_test.go new file mode 100644 index 00000000..c3a9e1e6 --- /dev/null +++ b/controlplane/serverconfig/vanilla_internal_test.go @@ -0,0 +1,43 @@ +package serverconfig + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/require" +) + +func TestMutateProperties(t *testing.T) { + input := `#Minecraft server properties +#Mon Mar 09 17:21:18 CET 2026 +log-ips=true +management-server-allowed-origins= +management-server-enabled=false +management-server-host=1.1.1.1 +management-server-port=1337 +management-server-secret=wrong +management-server-tls-enabled=false +online-mode=true +server-ip= +server-port=1337 +use-native-transport=true +` + expected := `log-ips = false +management-server-allowed-origins = * +management-server-enabled = true +management-server-host = localhost +management-server-port = 26656 +management-server-secret = change-me-later +management-server-tls-enabled = false +online-mode = false +server-ip = 0.0.0.0 +server-port = 25565 +use-native-transport = true +` + actual, err := sanatizeServerProperties([]byte(input)) + require.NoError(t, err) + + if d := cmp.Diff(expected, string(actual)); d != "" { + t.Fatalf("mismatch (-want +got):\n%s", d) + } +} diff --git a/controlplane/worker/create_image.go b/controlplane/worker/create_image.go index e6d5888a..7f69c132 100644 --- a/controlplane/worker/create_image.go +++ b/controlplane/worker/create_image.go @@ -32,6 +32,7 @@ import ( "github.com/spacechunks/explorer/controlplane/chunk" "github.com/spacechunks/explorer/controlplane/job" "github.com/spacechunks/explorer/controlplane/resource" + "github.com/spacechunks/explorer/controlplane/serverconfig" "github.com/spacechunks/explorer/internal/file" "github.com/spacechunks/explorer/internal/image" "github.com/spacechunks/explorer/internal/tarhelper" @@ -152,7 +153,14 @@ func (w *CreateImageWorker) Work(ctx context.Context, riverJob *river.Job[job.Cr return fmt.Errorf("download missing: %w", err) } - // TODO: adjust configs + rt, err := os.OpenRoot(serverRootDir) + if err != nil { + return fmt.Errorf("open root: %w", err) + } + + if err := serverconfig.SanitizeConfigs(rt); err != nil { + return fmt.Errorf("sanitize configs: %w", err) + } // it is VERY important we specify the parent of the server root directory, // because only paths starting INSIDE the passed directory are preserved. @@ -162,8 +170,6 @@ func (w *CreateImageWorker) Work(ctx context.Context, riverJob *river.Job[job.Cr return fmt.Errorf("append layer: %w", err) } - // FIXME: if we implement users add user id to ref - // => //: ref := fmt.Sprintf("%s/%s:base", riverJob.Args.OCIRegistry, riverJob.Args.FlavorVersionID) if err := w.imgService.Push(ctx, img, ref); err != nil { From 2158ffc99183761d94d527cb66dfcd8417d8467a Mon Sep 17 00:00:00 2001 From: yannic rieger Date: Sun, 22 Mar 2026 17:32:26 +0100 Subject: [PATCH 2/3] add properties parser module --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 8b9ddacb..bee2ef49 100644 --- a/go.mod +++ b/go.mod @@ -28,6 +28,7 @@ require ( github.com/jackc/pgx/v5 v5.7.4 github.com/johannesboyne/gofakes3 v0.0.0-20250825084532-6555d310c473 github.com/lestrrat-go/jwx/v3 v3.0.12 + github.com/magiconair/properties v1.8.10 github.com/peterbourgon/ff/v3 v3.4.0 github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c github.com/pkg/errors v0.9.1 @@ -124,7 +125,6 @@ require ( github.com/lib/pq v1.10.9 // indirect github.com/lmittmann/tint v1.0.7 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect - github.com/magiconair/properties v1.8.10 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.19 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect From b697ae1360c514873882dd10042beff2328c7e99 Mon Sep 17 00:00:00 2001 From: yannic rieger Date: Sun, 22 Mar 2026 17:36:46 +0100 Subject: [PATCH 3/3] please linter --- controlplane/serverconfig/paper.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controlplane/serverconfig/paper.go b/controlplane/serverconfig/paper.go index 39b591da..a2b25d36 100644 --- a/controlplane/serverconfig/paper.go +++ b/controlplane/serverconfig/paper.go @@ -24,7 +24,7 @@ type paperGlobal struct { type proxiesConfig struct { ProxyProtocol bool `json:"proxy-protocol"` - Velocity struct { + Velocity struct { Enabled bool `json:"enabled"` OnlineMode bool `json:"online-mode"` Secret string `json:"secret"`