This commit implements Phase 2 of the CHORUS Task Execution Engine development plan, providing a comprehensive execution environment abstraction layer with Docker container sandboxing support. ## New Features ### Core Sandbox Interface - Comprehensive ExecutionSandbox interface with isolated task execution - Support for command execution, file I/O, environment management - Resource usage monitoring and sandbox lifecycle management - Standardized error handling with SandboxError types and categories ### Docker Container Sandbox Implementation - Full Docker API integration with secure container creation - Transparent repository mounting with configurable read/write access - Advanced security policies with capability dropping and privilege controls - Comprehensive resource limits (CPU, memory, disk, processes, file handles) - Support for tmpfs mounts, masked paths, and read-only bind mounts - Container lifecycle management with proper cleanup and health monitoring ### Security & Resource Management - Configurable security policies with SELinux, AppArmor, and Seccomp support - Fine-grained capability management with secure defaults - Network isolation options with configurable DNS and proxy settings - Resource monitoring with real-time CPU, memory, and network usage tracking - Comprehensive ulimits configuration for process and file handle limits ### Repository Integration - Seamless repository mounting from local paths to container workspaces - Git configuration support with user credentials and global settings - File inclusion/exclusion patterns for selective repository access - Configurable permissions and ownership for mounted repositories ### Testing Infrastructure - Comprehensive test suite with 60+ test cases covering all functionality - Docker integration tests with Alpine Linux containers (skipped in short mode) - Mock sandbox implementation for unit testing without Docker dependencies - Security policy validation tests with read-only filesystem enforcement - Resource usage monitoring and cleanup verification tests ## Technical Details ### Dependencies Added - github.com/docker/docker v28.4.0+incompatible - Docker API client - github.com/docker/go-connections v0.6.0 - Docker connection utilities - github.com/docker/go-units v0.5.0 - Docker units and formatting - Associated Docker API dependencies for complete container management ### Architecture - Interface-driven design enabling multiple sandbox implementations - Comprehensive configuration structures for all sandbox aspects - Resource usage tracking with detailed metrics collection - Error handling with retryable error classification - Proper cleanup and resource management throughout sandbox lifecycle ### Compatibility - Maintains backward compatibility with existing CHORUS architecture - Designed for future integration with Phase 3 Core Task Execution Engine - Extensible design supporting additional sandbox implementations (VM, process) This Phase 2 implementation provides the foundation for secure, isolated task execution that will be integrated with the AI model providers from Phase 1 in the upcoming Phase 3 development. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
415 lines
15 KiB
Go
415 lines
15 KiB
Go
package execution
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"time"
|
|
)
|
|
|
|
// ExecutionSandbox defines the interface for isolated task execution environments
|
|
type ExecutionSandbox interface {
|
|
// Initialize sets up the sandbox environment
|
|
Initialize(ctx context.Context, config *SandboxConfig) error
|
|
|
|
// ExecuteCommand runs a command within the sandbox
|
|
ExecuteCommand(ctx context.Context, cmd *Command) (*CommandResult, error)
|
|
|
|
// CopyFiles copies files between host and sandbox
|
|
CopyFiles(ctx context.Context, source, dest string) error
|
|
|
|
// WriteFile writes content to a file in the sandbox
|
|
WriteFile(ctx context.Context, path string, content []byte, mode uint32) error
|
|
|
|
// ReadFile reads content from a file in the sandbox
|
|
ReadFile(ctx context.Context, path string) ([]byte, error)
|
|
|
|
// ListFiles lists files in a directory within the sandbox
|
|
ListFiles(ctx context.Context, path string) ([]FileInfo, error)
|
|
|
|
// GetWorkingDirectory returns the current working directory in the sandbox
|
|
GetWorkingDirectory() string
|
|
|
|
// SetWorkingDirectory changes the working directory in the sandbox
|
|
SetWorkingDirectory(path string) error
|
|
|
|
// GetEnvironment returns environment variables in the sandbox
|
|
GetEnvironment() map[string]string
|
|
|
|
// SetEnvironment sets environment variables in the sandbox
|
|
SetEnvironment(env map[string]string) error
|
|
|
|
// GetResourceUsage returns current resource usage statistics
|
|
GetResourceUsage(ctx context.Context) (*ResourceUsage, error)
|
|
|
|
// Cleanup destroys the sandbox and cleans up resources
|
|
Cleanup() error
|
|
|
|
// GetInfo returns information about the sandbox
|
|
GetInfo() SandboxInfo
|
|
}
|
|
|
|
// SandboxConfig represents configuration for a sandbox environment
|
|
type SandboxConfig struct {
|
|
// Sandbox type and runtime
|
|
Type string `json:"type"` // docker, vm, process
|
|
Image string `json:"image"` // Container/VM image
|
|
Runtime string `json:"runtime"` // docker, containerd, etc.
|
|
Architecture string `json:"architecture"` // amd64, arm64
|
|
|
|
// Resource limits
|
|
Resources ResourceLimits `json:"resources"`
|
|
|
|
// Security settings
|
|
Security SecurityPolicy `json:"security"`
|
|
|
|
// Repository configuration
|
|
Repository RepositoryConfig `json:"repository"`
|
|
|
|
// Network settings
|
|
Network NetworkConfig `json:"network"`
|
|
|
|
// Environment settings
|
|
Environment map[string]string `json:"environment"`
|
|
WorkingDir string `json:"working_dir"`
|
|
|
|
// Tool and service access
|
|
Tools []string `json:"tools"` // Available tools in sandbox
|
|
MCPServers []string `json:"mcp_servers"` // MCP servers to connect to
|
|
|
|
// Execution settings
|
|
Timeout time.Duration `json:"timeout"` // Maximum execution time
|
|
CleanupDelay time.Duration `json:"cleanup_delay"` // Delay before cleanup
|
|
|
|
// Metadata
|
|
Labels map[string]string `json:"labels"`
|
|
Annotations map[string]string `json:"annotations"`
|
|
}
|
|
|
|
// Command represents a command to execute in the sandbox
|
|
type Command struct {
|
|
// Command specification
|
|
Executable string `json:"executable"`
|
|
Args []string `json:"args"`
|
|
WorkingDir string `json:"working_dir"`
|
|
Environment map[string]string `json:"environment"`
|
|
|
|
// Input/Output
|
|
Stdin io.Reader `json:"-"`
|
|
StdinContent string `json:"stdin_content"`
|
|
|
|
// Execution settings
|
|
Timeout time.Duration `json:"timeout"`
|
|
User string `json:"user"`
|
|
|
|
// Security settings
|
|
AllowNetwork bool `json:"allow_network"`
|
|
AllowWrite bool `json:"allow_write"`
|
|
RestrictPaths []string `json:"restrict_paths"`
|
|
}
|
|
|
|
// CommandResult represents the result of command execution
|
|
type CommandResult struct {
|
|
// Exit information
|
|
ExitCode int `json:"exit_code"`
|
|
Success bool `json:"success"`
|
|
|
|
// Output
|
|
Stdout string `json:"stdout"`
|
|
Stderr string `json:"stderr"`
|
|
Combined string `json:"combined"`
|
|
|
|
// Timing
|
|
StartTime time.Time `json:"start_time"`
|
|
EndTime time.Time `json:"end_time"`
|
|
Duration time.Duration `json:"duration"`
|
|
|
|
// Resource usage during execution
|
|
ResourceUsage ResourceUsage `json:"resource_usage"`
|
|
|
|
// Error information
|
|
Error string `json:"error,omitempty"`
|
|
Signal string `json:"signal,omitempty"`
|
|
|
|
// Metadata
|
|
ProcessID int `json:"process_id,omitempty"`
|
|
Metadata map[string]interface{} `json:"metadata,omitempty"`
|
|
}
|
|
|
|
// FileInfo represents information about a file in the sandbox
|
|
type FileInfo struct {
|
|
Name string `json:"name"`
|
|
Path string `json:"path"`
|
|
Size int64 `json:"size"`
|
|
Mode uint32 `json:"mode"`
|
|
ModTime time.Time `json:"mod_time"`
|
|
IsDir bool `json:"is_dir"`
|
|
Owner string `json:"owner"`
|
|
Group string `json:"group"`
|
|
Permissions string `json:"permissions"`
|
|
}
|
|
|
|
// ResourceLimits defines resource constraints for the sandbox
|
|
type ResourceLimits struct {
|
|
// CPU limits
|
|
CPULimit float64 `json:"cpu_limit"` // CPU cores (e.g., 1.5)
|
|
CPURequest float64 `json:"cpu_request"` // CPU cores requested
|
|
|
|
// Memory limits
|
|
MemoryLimit int64 `json:"memory_limit"` // Bytes
|
|
MemoryRequest int64 `json:"memory_request"` // Bytes
|
|
|
|
// Storage limits
|
|
DiskLimit int64 `json:"disk_limit"` // Bytes
|
|
DiskRequest int64 `json:"disk_request"` // Bytes
|
|
|
|
// Network limits
|
|
NetworkInLimit int64 `json:"network_in_limit"` // Bytes/sec
|
|
NetworkOutLimit int64 `json:"network_out_limit"` // Bytes/sec
|
|
|
|
// Process limits
|
|
ProcessLimit int `json:"process_limit"` // Max processes
|
|
FileLimit int `json:"file_limit"` // Max open files
|
|
|
|
// Time limits
|
|
WallTimeLimit time.Duration `json:"wall_time_limit"` // Max wall clock time
|
|
CPUTimeLimit time.Duration `json:"cpu_time_limit"` // Max CPU time
|
|
}
|
|
|
|
// SecurityPolicy defines security constraints and policies
|
|
type SecurityPolicy struct {
|
|
// Container security
|
|
RunAsUser string `json:"run_as_user"`
|
|
RunAsGroup string `json:"run_as_group"`
|
|
ReadOnlyRoot bool `json:"read_only_root"`
|
|
NoNewPrivileges bool `json:"no_new_privileges"`
|
|
|
|
// Capabilities
|
|
AddCapabilities []string `json:"add_capabilities"`
|
|
DropCapabilities []string `json:"drop_capabilities"`
|
|
|
|
// SELinux/AppArmor
|
|
SELinuxContext string `json:"selinux_context"`
|
|
AppArmorProfile string `json:"apparmor_profile"`
|
|
SeccompProfile string `json:"seccomp_profile"`
|
|
|
|
// Network security
|
|
AllowNetworking bool `json:"allow_networking"`
|
|
AllowedHosts []string `json:"allowed_hosts"`
|
|
BlockedHosts []string `json:"blocked_hosts"`
|
|
AllowedPorts []int `json:"allowed_ports"`
|
|
|
|
// File system security
|
|
ReadOnlyPaths []string `json:"read_only_paths"`
|
|
MaskedPaths []string `json:"masked_paths"`
|
|
TmpfsPaths []string `json:"tmpfs_paths"`
|
|
|
|
// Resource protection
|
|
PreventEscalation bool `json:"prevent_escalation"`
|
|
IsolateNetwork bool `json:"isolate_network"`
|
|
IsolateProcess bool `json:"isolate_process"`
|
|
|
|
// Monitoring
|
|
EnableAuditLog bool `json:"enable_audit_log"`
|
|
LogSecurityEvents bool `json:"log_security_events"`
|
|
}
|
|
|
|
// RepositoryConfig defines how the repository is mounted in the sandbox
|
|
type RepositoryConfig struct {
|
|
// Repository source
|
|
URL string `json:"url"`
|
|
Branch string `json:"branch"`
|
|
CommitHash string `json:"commit_hash"`
|
|
LocalPath string `json:"local_path"`
|
|
|
|
// Mount configuration
|
|
MountPoint string `json:"mount_point"` // Path in sandbox
|
|
ReadOnly bool `json:"read_only"`
|
|
|
|
// Git configuration
|
|
GitConfig GitConfig `json:"git_config"`
|
|
|
|
// File filters
|
|
IncludeFiles []string `json:"include_files"` // Glob patterns
|
|
ExcludeFiles []string `json:"exclude_files"` // Glob patterns
|
|
|
|
// Access permissions
|
|
Permissions string `json:"permissions"` // rwx format
|
|
Owner string `json:"owner"`
|
|
Group string `json:"group"`
|
|
}
|
|
|
|
// GitConfig defines Git configuration within the sandbox
|
|
type GitConfig struct {
|
|
UserName string `json:"user_name"`
|
|
UserEmail string `json:"user_email"`
|
|
SigningKey string `json:"signing_key"`
|
|
ConfigValues map[string]string `json:"config_values"`
|
|
}
|
|
|
|
// NetworkConfig defines network settings for the sandbox
|
|
type NetworkConfig struct {
|
|
// Network isolation
|
|
Isolated bool `json:"isolated"` // No network access
|
|
Bridge string `json:"bridge"` // Network bridge
|
|
|
|
// DNS settings
|
|
DNSServers []string `json:"dns_servers"`
|
|
DNSSearch []string `json:"dns_search"`
|
|
|
|
// Proxy settings
|
|
HTTPProxy string `json:"http_proxy"`
|
|
HTTPSProxy string `json:"https_proxy"`
|
|
NoProxy string `json:"no_proxy"`
|
|
|
|
// Port mappings
|
|
PortMappings []PortMapping `json:"port_mappings"`
|
|
|
|
// Bandwidth limits
|
|
IngressLimit int64 `json:"ingress_limit"` // Bytes/sec
|
|
EgressLimit int64 `json:"egress_limit"` // Bytes/sec
|
|
}
|
|
|
|
// PortMapping defines port forwarding configuration
|
|
type PortMapping struct {
|
|
HostPort int `json:"host_port"`
|
|
ContainerPort int `json:"container_port"`
|
|
Protocol string `json:"protocol"` // tcp, udp
|
|
}
|
|
|
|
// ResourceUsage represents current resource consumption
|
|
type ResourceUsage struct {
|
|
// Timestamp of measurement
|
|
Timestamp time.Time `json:"timestamp"`
|
|
|
|
// CPU usage
|
|
CPUUsage float64 `json:"cpu_usage"` // Percentage
|
|
CPUTime time.Duration `json:"cpu_time"` // Total CPU time
|
|
|
|
// Memory usage
|
|
MemoryUsage int64 `json:"memory_usage"` // Bytes
|
|
MemoryPercent float64 `json:"memory_percent"` // Percentage of limit
|
|
MemoryPeak int64 `json:"memory_peak"` // Peak usage
|
|
|
|
// Disk usage
|
|
DiskUsage int64 `json:"disk_usage"` // Bytes
|
|
DiskReads int64 `json:"disk_reads"` // Read operations
|
|
DiskWrites int64 `json:"disk_writes"` // Write operations
|
|
|
|
// Network usage
|
|
NetworkIn int64 `json:"network_in"` // Bytes received
|
|
NetworkOut int64 `json:"network_out"` // Bytes sent
|
|
|
|
// Process information
|
|
ProcessCount int `json:"process_count"` // Active processes
|
|
ThreadCount int `json:"thread_count"` // Active threads
|
|
FileHandles int `json:"file_handles"` // Open file handles
|
|
|
|
// Runtime information
|
|
Uptime time.Duration `json:"uptime"` // Sandbox uptime
|
|
}
|
|
|
|
// SandboxInfo provides information about a sandbox instance
|
|
type SandboxInfo struct {
|
|
// Identification
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
Type string `json:"type"`
|
|
|
|
// Status
|
|
Status SandboxStatus `json:"status"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
StartedAt time.Time `json:"started_at"`
|
|
|
|
// Runtime information
|
|
Runtime string `json:"runtime"`
|
|
Image string `json:"image"`
|
|
Platform string `json:"platform"`
|
|
|
|
// Network information
|
|
IPAddress string `json:"ip_address"`
|
|
MACAddress string `json:"mac_address"`
|
|
Hostname string `json:"hostname"`
|
|
|
|
// Resource information
|
|
AllocatedResources ResourceLimits `json:"allocated_resources"`
|
|
|
|
// Configuration
|
|
Config SandboxConfig `json:"config"`
|
|
|
|
// Metadata
|
|
Labels map[string]string `json:"labels"`
|
|
Annotations map[string]string `json:"annotations"`
|
|
}
|
|
|
|
// SandboxStatus represents the current status of a sandbox
|
|
type SandboxStatus string
|
|
|
|
const (
|
|
StatusCreating SandboxStatus = "creating"
|
|
StatusStarting SandboxStatus = "starting"
|
|
StatusRunning SandboxStatus = "running"
|
|
StatusPaused SandboxStatus = "paused"
|
|
StatusStopping SandboxStatus = "stopping"
|
|
StatusStopped SandboxStatus = "stopped"
|
|
StatusFailed SandboxStatus = "failed"
|
|
StatusDestroyed SandboxStatus = "destroyed"
|
|
)
|
|
|
|
// Common sandbox errors
|
|
var (
|
|
ErrSandboxNotFound = &SandboxError{Code: "SANDBOX_NOT_FOUND", Message: "Sandbox not found"}
|
|
ErrSandboxAlreadyExists = &SandboxError{Code: "SANDBOX_ALREADY_EXISTS", Message: "Sandbox already exists"}
|
|
ErrSandboxNotRunning = &SandboxError{Code: "SANDBOX_NOT_RUNNING", Message: "Sandbox is not running"}
|
|
ErrSandboxInitFailed = &SandboxError{Code: "SANDBOX_INIT_FAILED", Message: "Sandbox initialization failed"}
|
|
ErrCommandExecutionFailed = &SandboxError{Code: "COMMAND_EXECUTION_FAILED", Message: "Command execution failed"}
|
|
ErrResourceLimitExceeded = &SandboxError{Code: "RESOURCE_LIMIT_EXCEEDED", Message: "Resource limit exceeded"}
|
|
ErrSecurityViolation = &SandboxError{Code: "SECURITY_VIOLATION", Message: "Security policy violation"}
|
|
ErrFileOperationFailed = &SandboxError{Code: "FILE_OPERATION_FAILED", Message: "File operation failed"}
|
|
ErrNetworkAccessDenied = &SandboxError{Code: "NETWORK_ACCESS_DENIED", Message: "Network access denied"}
|
|
ErrTimeoutExceeded = &SandboxError{Code: "TIMEOUT_EXCEEDED", Message: "Execution timeout exceeded"}
|
|
)
|
|
|
|
// SandboxError represents sandbox-specific errors
|
|
type SandboxError struct {
|
|
Code string `json:"code"`
|
|
Message string `json:"message"`
|
|
Details string `json:"details,omitempty"`
|
|
Retryable bool `json:"retryable"`
|
|
Cause error `json:"-"`
|
|
}
|
|
|
|
func (e *SandboxError) Error() string {
|
|
if e.Details != "" {
|
|
return e.Message + ": " + e.Details
|
|
}
|
|
return e.Message
|
|
}
|
|
|
|
func (e *SandboxError) Unwrap() error {
|
|
return e.Cause
|
|
}
|
|
|
|
func (e *SandboxError) IsRetryable() bool {
|
|
return e.Retryable
|
|
}
|
|
|
|
// NewSandboxError creates a new sandbox error with details
|
|
func NewSandboxError(base *SandboxError, details string) *SandboxError {
|
|
return &SandboxError{
|
|
Code: base.Code,
|
|
Message: base.Message,
|
|
Details: details,
|
|
Retryable: base.Retryable,
|
|
}
|
|
}
|
|
|
|
// NewSandboxErrorWithCause creates a new sandbox error with an underlying cause
|
|
func NewSandboxErrorWithCause(base *SandboxError, details string, cause error) *SandboxError {
|
|
return &SandboxError{
|
|
Code: base.Code,
|
|
Message: base.Message,
|
|
Details: details,
|
|
Retryable: base.Retryable,
|
|
Cause: cause,
|
|
}
|
|
} |