Phase 2: Implement Execution Environment Abstraction (v0.3.0)
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>
This commit is contained in:
415
pkg/execution/sandbox.go
Normal file
415
pkg/execution/sandbox.go
Normal file
@@ -0,0 +1,415 @@
|
||||
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,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user