fetch_ml/internal/container/podman.go
Jeremie Fraeys ea15af1833 Fix multi-user authentication and clean up debug code
- Fix YAML tags in auth config struct (json -> yaml)
- Update CLI configs to use pre-hashed API keys
- Remove double hashing in WebSocket client
- Fix port mapping (9102 -> 9103) in CLI commands
- Update permission keys to use jobs:read, jobs:create, etc.
- Clean up all debug logging from CLI and server
- All user roles now authenticate correctly:
  * Admin: Can queue jobs and see all jobs
  * Researcher: Can queue jobs and see own jobs
  * Analyst: Can see status (read-only access)

Multi-user authentication is now fully functional.
2025-12-06 12:35:32 -05:00

111 lines
2.7 KiB
Go

// Package container provides Podman container management utilities.
package container
import (
"context"
"fmt"
"os/exec"
"path/filepath"
"strings"
"github.com/jfraeys/fetch_ml/internal/config"
)
// PodmanConfig holds configuration for Podman container execution
type PodmanConfig struct {
Image string
Workspace string
Results string
ContainerWorkspace string
ContainerResults string
GPUAccess bool
Memory string
CPUs string
}
// BuildPodmanCommand builds a Podman command for executing ML experiments
func BuildPodmanCommand(
ctx context.Context,
cfg PodmanConfig,
scriptPath, requirementsPath string,
extraArgs []string,
) *exec.Cmd {
args := []string{
"run", "--rm",
"--security-opt", "no-new-privileges",
"--cap-drop", "ALL",
}
if cfg.Memory != "" {
args = append(args, "--memory", cfg.Memory)
} else {
args = append(args, "--memory", config.DefaultPodmanMemory)
}
if cfg.CPUs != "" {
args = append(args, "--cpus", cfg.CPUs)
} else {
args = append(args, "--cpus", config.DefaultPodmanCPUs)
}
args = append(args, "--userns", "keep-id")
// Mount workspace
workspaceMount := fmt.Sprintf("%s:%s:rw", cfg.Workspace, cfg.ContainerWorkspace)
args = append(args, "-v", workspaceMount)
// Mount results
resultsMount := fmt.Sprintf("%s:%s:rw", cfg.Results, cfg.ContainerResults)
args = append(args, "-v", resultsMount)
if cfg.GPUAccess {
args = append(args, "--device", "/dev/dri")
}
// Image and command
args = append(args, cfg.Image,
"--workspace", cfg.ContainerWorkspace,
"--requirements", requirementsPath,
"--script", scriptPath,
)
// Add extra arguments via --args flag
if len(extraArgs) > 0 {
args = append(args, "--args")
args = append(args, extraArgs...)
}
return exec.CommandContext(ctx, "podman", args...)
}
// SanitizePath ensures a path is safe to use (prevents path traversal)
func SanitizePath(path string) (string, error) {
// Clean the path to remove any .. or . components
cleaned := filepath.Clean(path)
// Check for path traversal attempts
if strings.Contains(cleaned, "..") {
return "", fmt.Errorf("path traversal detected: %s", path)
}
return cleaned, nil
}
// ValidateJobName validates a job name is safe
func ValidateJobName(jobName string) error {
if jobName == "" {
return fmt.Errorf("job name cannot be empty")
}
// Check for dangerous characters
if strings.ContainsAny(jobName, "/\\<>:\"|?*") {
return fmt.Errorf("job name contains invalid characters: %s", jobName)
}
// Check for path traversal
if strings.Contains(jobName, "..") {
return fmt.Errorf("job name contains path traversal: %s", jobName)
}
return nil
}