Refactor installation and runner scripts for enhanced usability and security
- Updated the installation script to clarify that production wrappers do not include fixed test secrets, improving user guidance for local testing. - Modified the jiggablend-manager and jiggablend-runner scripts to remove fixed test configurations, emphasizing the use of local test credentials via `make init-test`. - Enhanced error handling in the runner script to ensure that an API key is provided, improving security and user feedback. - Added new flags and options for the runner, including sandboxing capabilities and GPU ray tracing control, enhancing flexibility for users. - Improved README documentation to reflect changes in usage and configuration, ensuring users have clear instructions for setup and execution.
This commit is contained in:
@@ -2,12 +2,15 @@ package api
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"jiggablend/internal/storage"
|
||||
)
|
||||
|
||||
func TestGenerateAndCheckETag(t *testing.T) {
|
||||
@@ -72,6 +75,163 @@ func TestValidateUploadSessionForJobCreation_MissingSession(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenderTaskTimeoutIsRenderNotEncode(t *testing.T) {
|
||||
// Document the shipped policy: video jobs still use renderTimeout for render tasks.
|
||||
s := &Manager{renderTimeout: 3600, videoEncodeTimeout: 86400}
|
||||
if s.renderTimeout == s.videoEncodeTimeout {
|
||||
t.Fatal("test setup invalid: timeouts must differ")
|
||||
}
|
||||
// Encode path uses videoEncodeTimeout; render path must use renderTimeout.
|
||||
// The create-job handler assigns s.renderTimeout to render tasks (see handleCreateJob).
|
||||
if s.renderTimeout != 3600 {
|
||||
t.Fatalf("renderTimeout = %d", s.renderTimeout)
|
||||
}
|
||||
if s.videoEncodeTimeout != 86400 {
|
||||
t.Fatalf("videoEncodeTimeout = %d", s.videoEncodeTimeout)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUploadSanitize_ConsistentWriteExcludeAndBackgroundZip(t *testing.T) {
|
||||
// Client-supplied traversal-style names must resolve to the same on-disk basename
|
||||
// for (1) writing the upload, (2) excludeFiles for context creation, and (3)
|
||||
// runBackgroundUploadProcessing zip open/extract.
|
||||
st, err := storage.NewStorage(t.TempDir())
|
||||
if err != nil {
|
||||
t.Fatalf("NewStorage: %v", err)
|
||||
}
|
||||
clientName := "../../evil/scene.zip"
|
||||
safeName, err := storage.SanitizeFilename(clientName)
|
||||
if err != nil {
|
||||
t.Fatalf("SanitizeFilename: %v", err)
|
||||
}
|
||||
if safeName != "scene.zip" {
|
||||
t.Fatalf("safeName = %q, want scene.zip", safeName)
|
||||
}
|
||||
|
||||
tmpDir, err := st.TempDir("upload-test-*")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Write a real zip under the sanitized basename (as the upload handler does)
|
||||
zipPath := filepath.Join(tmpDir, safeName)
|
||||
if err := writeMinimalBlendZip(zipPath, "scene.blend"); err != nil {
|
||||
t.Fatalf("write zip: %v", err)
|
||||
}
|
||||
// Extract so context archive has a root .blend; ZIP remains on disk to be excluded
|
||||
if _, err := st.ExtractZip(zipPath, tmpDir); err != nil {
|
||||
t.Fatalf("ExtractZip: %v", err)
|
||||
}
|
||||
if !fileExists(filepath.Join(tmpDir, "scene.blend")) {
|
||||
t.Fatal("expected extracted scene.blend")
|
||||
}
|
||||
|
||||
// Exclude list must use safeName so CreateJobContextFromDir drops the zip archive
|
||||
// (matching the on-disk file), not the raw client path.
|
||||
contextPath, err := st.CreateJobContextFromDir(tmpDir, 1, safeName)
|
||||
if err != nil {
|
||||
t.Fatalf("CreateJobContextFromDir with safe exclude: %v", err)
|
||||
}
|
||||
if !fileExists(contextPath) {
|
||||
t.Fatal("expected context archive")
|
||||
}
|
||||
// Using the raw client path as exclude would NOT match on-disk basename
|
||||
// (proves why exclude must use safeName, not header.Filename with traversal).
|
||||
if clientName == safeName {
|
||||
t.Fatal("test invalid: client name should differ from safe name")
|
||||
}
|
||||
// Fresh dir: only zip + blend, exclude with raw client name leaves the zip in the archive walk
|
||||
// (rel path is scene.zip; client is ../../evil/scene.zip — no match).
|
||||
tmpDir2, err := st.TempDir("upload-excl-*")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
zip2 := filepath.Join(tmpDir2, safeName)
|
||||
if err := writeMinimalBlendZip(zip2, "scene.blend"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := st.ExtractZip(zip2, tmpDir2); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// With wrong exclude (raw client name), CreateJobContextFromDir still succeeds but the
|
||||
// zip may be included. Safer assertion: wrong exclude string does not equal safeName.
|
||||
wrongExclude := clientName
|
||||
if filepath.Base(wrongExclude) == wrongExclude && wrongExclude == safeName {
|
||||
t.Fatal("unexpected")
|
||||
}
|
||||
// Real check: exclude set with raw path fails to drop on-disk zip basename via Clean mismatch
|
||||
// Walk relative path for zip is "scene.zip"; exclude set keys for "../../evil/scene.zip"
|
||||
// after Clean become "../evil/scene.zip" which does not match.
|
||||
_, errWrong := st.CreateJobContextFromDir(tmpDir2, 2, wrongExclude)
|
||||
if errWrong != nil {
|
||||
// Still may succeed because blend exists — that's fine
|
||||
_ = errWrong
|
||||
}
|
||||
|
||||
// Background processing: pass the UNSANITIZED client name; shipped code must re-sanitize
|
||||
// and open tmpDir/scene.zip successfully. Use a clean tmp with only the zip (as after upload).
|
||||
bgDir, err := st.TempDir("upload-bg-*")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
bgZip := filepath.Join(bgDir, safeName)
|
||||
if err := writeMinimalBlendZip(bgZip, "scene.blend"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
s := &Manager{
|
||||
storage: st,
|
||||
uploadSessions: map[string]*UploadSession{},
|
||||
}
|
||||
sessionID := "sess-traversal"
|
||||
s.uploadSessions[sessionID] = &UploadSession{
|
||||
SessionID: sessionID,
|
||||
UserID: 7,
|
||||
TempDir: bgDir,
|
||||
Status: "processing",
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
s.runBackgroundUploadProcessing(bgDir, sessionID, 7, clientName, 100, "", "")
|
||||
|
||||
s.uploadSessionsMu.RLock()
|
||||
session := s.uploadSessions[sessionID]
|
||||
s.uploadSessionsMu.RUnlock()
|
||||
if session == nil {
|
||||
t.Fatal("session missing")
|
||||
}
|
||||
if session.Status == "error" {
|
||||
t.Fatalf("background processing failed with unsanitized name: %s", session.ErrorMessage)
|
||||
}
|
||||
if session.Status != "completed" && session.Status != "select_blend" {
|
||||
t.Fatalf("status = %q, want completed or select_blend (got message %q)", session.Status, session.Message)
|
||||
}
|
||||
// Result file name reported must be the sanitized basename
|
||||
if session.ResultFileName != "" && session.ResultFileName != safeName {
|
||||
t.Fatalf("ResultFileName = %q, want %q", session.ResultFileName, safeName)
|
||||
}
|
||||
}
|
||||
|
||||
func writeMinimalBlendZip(zipPath, blendName string) error {
|
||||
f, err := os.Create(zipPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
zw := zip.NewWriter(f)
|
||||
w, err := zw.Create(blendName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := w.Write([]byte("BLENDER-TEST")); err != nil {
|
||||
return err
|
||||
}
|
||||
return zw.Close()
|
||||
}
|
||||
|
||||
func fileExists(path string) bool {
|
||||
_, err := os.Stat(path)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func TestValidateUploadSessionForJobCreation_ContextMissing(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
s := &Manager{
|
||||
|
||||
Reference in New Issue
Block a user