Files
DoGaMa-serv/internal/module/runtime_test.go
T
codex 35c11aff6d
CI / validate (pull_request) Successful in 26m22s
feat(vrising): support template runtime requirements
2026-08-25 22:32:15 +02:00

270 lines
11 KiB
Go

package module
import (
"context"
"crypto/sha256"
"encoding/binary"
"encoding/hex"
"errors"
"io"
"net"
"net/http"
"os"
"strings"
"testing"
"time"
"github.com/tetratelabs/wazero"
)
func sourceRCONPacket(id, typ uint32, body string) []byte {
value := make([]byte, len(body)+14)
binary.LittleEndian.PutUint32(value[:4], uint32(len(body)+10))
binary.LittleEndian.PutUint32(value[4:8], id)
binary.LittleEndian.PutUint32(value[8:12], typ)
copy(value[12:], body)
return value
}
type roundTripFunc func(*http.Request) (*http.Response, error)
func (f roundTripFunc) RoundTrip(request *http.Request) (*http.Response, error) { return f(request) }
func TestValidRelativeAPIPath(t *testing.T) {
for _, value := range []string{"http://metadata/v1/api/info", "//other/v1/api/info", "/v1/api/../secret", "/v1/api/info?q=1", "/etc/passwd"} {
if validRelativeAPIPath(value) {
t.Fatalf("unsafe path accepted: %q", value)
}
}
if !validRelativeAPIPath("/v1/api/info") {
t.Fatal("documented Palworld endpoint was rejected")
}
}
func TestPalworldAdapterReportsBoundedFailures(t *testing.T) {
wasm, err := os.ReadFile("../../catalog/palworld/module/module.wasm")
if err != nil {
t.Fatal(err)
}
digest := sha256.Sum256(wasm)
tests := []struct {
name string
transport roundTripFunc
code string
}{
{name: "unauthorized", code: "unauthorized", transport: func(request *http.Request) (*http.Response, error) {
return &http.Response{StatusCode: http.StatusUnauthorized, Header: make(http.Header), Body: io.NopCloser(strings.NewReader("unauthorized")), Request: request}, nil
}},
{name: "malformed", code: "invalid_response", transport: func(request *http.Request) (*http.Response, error) {
return &http.Response{StatusCode: http.StatusOK, Header: make(http.Header), Body: io.NopCloser(strings.NewReader("not-json")), Request: request}, nil
}},
{name: "offline", code: "unreachable", transport: func(*http.Request) (*http.Response, error) {
return nil, errors.New("offline")
}},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
runtime, err := newWithTransport(wasm, hex.EncodeToString(digest[:]), []string{"server_info"}, Limits{MemoryMB: 32, Timeout: 10 * time.Second, MaxResponseBytes: 1 << 20, MaxConcurrentCall: 2}, Binding{InstanceID: strings.Repeat("a", 20), ContainerPort: 8212, AllowedMethods: map[string]bool{"GET": true}, Configuration: map[string]string{"username": "admin"}, Secrets: map[string]string{"admin_password": "secret"}}, test.transport)
if err != nil {
t.Fatal(err)
}
var response struct {
OK bool `json:"ok"`
Data map[string]any `json:"data"`
Error *struct {
Code string `json:"code"`
Message string `json:"message"`
Retryable bool `json:"retryable"`
} `json:"error"`
}
if err := runtime.Call(context.Background(), "get_server_info", struct{}{}, &response); err != nil {
t.Fatal(err)
}
if response.OK || response.Error == nil || response.Error.Code != test.code || strings.Contains(response.Error.Message, "secret") {
t.Fatalf("unexpected safe failure: %+v", response)
}
})
}
}
func TestPalworldAdapterExecutesInSandbox(t *testing.T) {
wasm, err := os.ReadFile("../../catalog/palworld/module/module.wasm")
if err != nil {
t.Fatal(err)
}
digest := sha256.Sum256(wasm)
transport := roundTripFunc(func(request *http.Request) (*http.Response, error) {
if request.URL.Path != "/v1/api/info" || request.Header.Get("Authorization") == "" {
t.Fatalf("unexpected adapter request: %s", request.URL)
}
return &http.Response{StatusCode: 200, Header: http.Header{"Content-Type": {"application/json"}}, Body: io.NopCloser(strings.NewReader(`{"version":"v1","servername":"test","description":"fixture","worldguid":"world"}`)), Request: request}, nil
})
runtime, err := newWithTransport(wasm, hex.EncodeToString(digest[:]), []string{"server_info"}, Limits{MemoryMB: 32, Timeout: 10 * time.Second, MaxResponseBytes: 1 << 20, MaxConcurrentCall: 2}, Binding{InstanceID: strings.Repeat("a", 20), ContainerPort: 8212, AllowedMethods: map[string]bool{"GET": true, "POST": true}, Configuration: map[string]string{"username": "admin"}, Secrets: map[string]string{"admin_password": "secret"}}, transport)
if err != nil {
t.Fatal(err)
}
var response struct {
OK bool `json:"ok"`
Error any `json:"error"`
Data struct {
Name string `json:"name"`
GameVersion string `json:"game_version"`
Description string `json:"description"`
WorldID string `json:"world_id"`
} `json:"data"`
}
if err := runtime.Call(context.Background(), "get_server_info", struct{}{}, &response); err != nil {
t.Fatal(err)
}
if !response.OK || response.Data.Name != "test" {
t.Fatalf("unexpected normalized response: %+v", response)
}
}
func TestNewRejectsChecksumAndUnknownCapability(t *testing.T) {
limits := Limits{MemoryMB: 32, Timeout: time.Second, MaxResponseBytes: 1024, MaxConcurrentCall: 1}
binding := Binding{InstanceID: strings.Repeat("a", 20), ContainerPort: 8212}
if _, err := newWithTransport([]byte("wasm"), strings.Repeat("0", 64), nil, limits, binding, roundTripFunc(nil)); err == nil {
t.Fatal("checksum mismatch accepted")
}
digest := sha256.Sum256([]byte("wasm"))
if _, err := newWithTransport([]byte("wasm"), hex.EncodeToString(digest[:]), []string{"shell"}, limits, binding, roundTripFunc(nil)); err == nil {
t.Fatal("unknown capability accepted")
}
}
func TestHTTPRequestPinsInstanceOriginAndDisablesRedirects(t *testing.T) {
instanceID := strings.Repeat("A", 20)
seen := false
transport := roundTripFunc(func(request *http.Request) (*http.Response, error) {
seen = true
if request.URL.String() != "http://dogama-aaaaaaaaaaaaaaaaaaaa:8212/v1/api/info" {
t.Fatalf("unexpected destination %q", request.URL)
}
return &http.Response{StatusCode: 200, Header: make(http.Header), Body: io.NopCloser(strings.NewReader(`{"ok":true}`)), Request: request}, nil
})
digest := sha256.Sum256([]byte("wasm"))
runtime, err := newWithTransport([]byte("wasm"), hex.EncodeToString(digest[:]), nil, Limits{MemoryMB: 32, Timeout: time.Second, MaxResponseBytes: 1024, MaxConcurrentCall: 1}, Binding{InstanceID: instanceID, ContainerPort: 8212, AllowedMethods: map[string]bool{"GET": true}}, transport)
if err != nil {
t.Fatal(err)
}
request, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "http://dogama-aaaaaaaaaaaaaaaaaaaa:8212/v1/api/info", nil)
response, err := runtime.client.Do(request)
if err != nil {
t.Fatal(err)
}
_ = response.Body.Close()
if !seen {
t.Fatal("bound transport was not used")
}
}
func TestVRisingAdapterUsesPinnedBoundedTCPRCON(t *testing.T) {
wasm, err := os.ReadFile("../../catalog/vrising/module/module.wasm")
if err != nil {
t.Fatal(err)
}
digest := sha256.Sum256(wasm)
var seen []byte
dial := func(context.Context, string, string) (net.Conn, error) {
client, server := net.Pipe()
go func() {
defer server.Close()
buffer := make([]byte, 4096)
n, readErr := server.Read(buffer)
if readErr != nil {
return
}
seen = append([]byte(nil), buffer[:n]...)
_, _ = server.Write(sourceRCONPacket(1, 2, ""))
}()
return client, nil
}
runtime, err := newWithTransportAndDial(wasm, hex.EncodeToString(digest[:]), nil, Limits{MemoryMB: 64, Timeout: 10 * time.Second, MaxResponseBytes: 262144, MaxConcurrentCall: 2}, Binding{InstanceID: strings.Repeat("a", 20), ContainerPort: 9878, Configuration: map[string]string{"rcon_enabled": "true", "rcon_port": "9878"}, Secrets: map[string]string{"rcon_password": "not-in-output"}}, roundTripFunc(func(*http.Request) (*http.Response, error) { return nil, errors.New("HTTP must not be used") }), dial)
if err != nil {
t.Fatal(err)
}
var response struct {
OK bool `json:"ok"`
Data any `json:"data"`
Error any `json:"error"`
}
if err := runtime.Call(context.Background(), "test_connection", struct{}{}, &response); err != nil || !response.OK {
t.Fatalf("connection = %#v, %v", response, err)
}
if len(seen) < 14 || binary.LittleEndian.Uint32(seen[8:12]) != 3 {
t.Fatalf("unexpected RCON auth packet: %x", seen)
}
}
func TestVRisingAdapterRejectsAuthenticationWithoutLeakingSecret(t *testing.T) {
wasm, err := os.ReadFile("../../catalog/vrising/module/module.wasm")
if err != nil {
t.Fatal(err)
}
digest := sha256.Sum256(wasm)
dial := func(context.Context, string, string) (net.Conn, error) {
client, server := net.Pipe()
go func() {
defer server.Close()
buffer := make([]byte, 256)
_, _ = server.Read(buffer)
_, _ = server.Write(sourceRCONPacket(^uint32(0), 2, ""))
}()
return client, nil
}
runtime, err := newWithTransportAndDial(wasm, hex.EncodeToString(digest[:]), nil, Limits{MemoryMB: 32, Timeout: 10 * time.Second, MaxResponseBytes: 262144, MaxConcurrentCall: 1}, Binding{InstanceID: strings.Repeat("a", 20), ContainerPort: 9878, Configuration: map[string]string{"rcon_enabled": "true", "rcon_port": "9878"}, Secrets: map[string]string{"rcon_password": "very-secret-value"}}, roundTripFunc(func(*http.Request) (*http.Response, error) { return nil, errors.New("HTTP must not be used") }), dial)
if err != nil {
t.Fatal(err)
}
var response struct {
OK bool `json:"ok"`
Data any `json:"data"`
Error struct {
Code string `json:"code"`
Message string `json:"message"`
Retryable bool `json:"retryable"`
} `json:"error"`
}
if err := runtime.Call(context.Background(), "test_connection", struct{}{}, &response); err != nil || response.OK || response.Error.Code != "unauthorized" || strings.Contains(response.Error.Message, "very-secret-value") {
t.Fatalf("unsafe authentication response: %#v, %v", response, err)
}
}
func TestVRisingAuthSuccessWithBothWazeroEngines(t *testing.T) {
wasm, err := os.ReadFile("../../catalog/vrising/module/module.wasm")
if err != nil {
t.Fatal(err)
}
digest := sha256.Sum256(wasm)
dial := func(context.Context, string, string) (net.Conn, error) {
client, server := net.Pipe()
go func() {
defer server.Close()
buffer := make([]byte, 256)
if _, err := server.Read(buffer); err == nil {
_, _ = server.Write(sourceRCONPacket(1, 2, ""))
}
}()
return client, nil
}
runtime, err := newWithTransportAndDial(wasm, hex.EncodeToString(digest[:]), nil, Limits{MemoryMB: 32, Timeout: 10 * time.Second, MaxResponseBytes: 262144, MaxConcurrentCall: 1}, Binding{InstanceID: strings.Repeat("a", 20), ContainerPort: 9878, Configuration: map[string]string{"rcon_enabled": "true", "rcon_port": "9878"}, Secrets: map[string]string{"rcon_password": "secret"}}, roundTripFunc(func(*http.Request) (*http.Response, error) { return nil, errors.New("HTTP must not be used") }), dial)
if err != nil {
t.Fatal(err)
}
for _, engine := range []struct {
name string
config wazero.RuntimeConfig
}{
{name: "compiler", config: wazero.NewRuntimeConfigCompiler()},
{name: "interpreter", config: wazero.NewRuntimeConfigInterpreter()},
} {
t.Run(engine.name, func(t *testing.T) {
result, err := runtime.invokeWithConfig(context.Background(), "test_connection", []byte("{}"), engine.config)
if err != nil || !strings.Contains(string(result), `"connected":true`) {
t.Fatalf("auth success result=%s err=%v", result, err)
}
})
}
}