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>
146 lines
5.0 KiB
Go
146 lines
5.0 KiB
Go
// Copyright 2019 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package protodesc
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"sync"
|
|
|
|
"google.golang.org/protobuf/internal/editiondefaults"
|
|
"google.golang.org/protobuf/internal/filedesc"
|
|
"google.golang.org/protobuf/proto"
|
|
"google.golang.org/protobuf/reflect/protoreflect"
|
|
"google.golang.org/protobuf/types/descriptorpb"
|
|
gofeaturespb "google.golang.org/protobuf/types/gofeaturespb"
|
|
)
|
|
|
|
var defaults = &descriptorpb.FeatureSetDefaults{}
|
|
var defaultsCacheMu sync.Mutex
|
|
var defaultsCache = make(map[filedesc.Edition]*descriptorpb.FeatureSet)
|
|
|
|
func init() {
|
|
err := proto.Unmarshal(editiondefaults.Defaults, defaults)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "unmarshal editions defaults: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
func fromEditionProto(epb descriptorpb.Edition) filedesc.Edition {
|
|
return filedesc.Edition(epb)
|
|
}
|
|
|
|
func toEditionProto(ed filedesc.Edition) descriptorpb.Edition {
|
|
switch ed {
|
|
case filedesc.EditionUnknown:
|
|
return descriptorpb.Edition_EDITION_UNKNOWN
|
|
case filedesc.EditionProto2:
|
|
return descriptorpb.Edition_EDITION_PROTO2
|
|
case filedesc.EditionProto3:
|
|
return descriptorpb.Edition_EDITION_PROTO3
|
|
case filedesc.Edition2023:
|
|
return descriptorpb.Edition_EDITION_2023
|
|
default:
|
|
panic(fmt.Sprintf("unknown value for edition: %v", ed))
|
|
}
|
|
}
|
|
|
|
func getFeatureSetFor(ed filedesc.Edition) *descriptorpb.FeatureSet {
|
|
defaultsCacheMu.Lock()
|
|
defer defaultsCacheMu.Unlock()
|
|
if def, ok := defaultsCache[ed]; ok {
|
|
return def
|
|
}
|
|
edpb := toEditionProto(ed)
|
|
if defaults.GetMinimumEdition() > edpb || defaults.GetMaximumEdition() < edpb {
|
|
// This should never happen protodesc.(FileOptions).New would fail when
|
|
// initializing the file descriptor.
|
|
// This most likely means the embedded defaults were not updated.
|
|
fmt.Fprintf(os.Stderr, "internal error: unsupported edition %v (did you forget to update the embedded defaults (i.e. the bootstrap descriptor proto)?)\n", edpb)
|
|
os.Exit(1)
|
|
}
|
|
fsed := defaults.GetDefaults()[0]
|
|
// Using a linear search for now.
|
|
// Editions are guaranteed to be sorted and thus we could use a binary search.
|
|
// Given that there are only a handful of editions (with one more per year)
|
|
// there is not much reason to use a binary search.
|
|
for _, def := range defaults.GetDefaults() {
|
|
if def.GetEdition() <= edpb {
|
|
fsed = def
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
fs := proto.Clone(fsed.GetFixedFeatures()).(*descriptorpb.FeatureSet)
|
|
proto.Merge(fs, fsed.GetOverridableFeatures())
|
|
defaultsCache[ed] = fs
|
|
return fs
|
|
}
|
|
|
|
// mergeEditionFeatures merges the parent and child feature sets. This function
|
|
// should be used when initializing Go descriptors from descriptor protos which
|
|
// is why the parent is a filedesc.EditionsFeatures (Go representation) while
|
|
// the child is a descriptorproto.FeatureSet (protoc representation).
|
|
// Any feature set by the child overwrites what is set by the parent.
|
|
func mergeEditionFeatures(parentDesc protoreflect.Descriptor, child *descriptorpb.FeatureSet) filedesc.EditionFeatures {
|
|
var parentFS filedesc.EditionFeatures
|
|
switch p := parentDesc.(type) {
|
|
case *filedesc.File:
|
|
parentFS = p.L1.EditionFeatures
|
|
case *filedesc.Message:
|
|
parentFS = p.L1.EditionFeatures
|
|
default:
|
|
panic(fmt.Sprintf("unknown parent type %T", parentDesc))
|
|
}
|
|
if child == nil {
|
|
return parentFS
|
|
}
|
|
if fp := child.FieldPresence; fp != nil {
|
|
parentFS.IsFieldPresence = *fp == descriptorpb.FeatureSet_LEGACY_REQUIRED ||
|
|
*fp == descriptorpb.FeatureSet_EXPLICIT
|
|
parentFS.IsLegacyRequired = *fp == descriptorpb.FeatureSet_LEGACY_REQUIRED
|
|
}
|
|
if et := child.EnumType; et != nil {
|
|
parentFS.IsOpenEnum = *et == descriptorpb.FeatureSet_OPEN
|
|
}
|
|
|
|
if rfe := child.RepeatedFieldEncoding; rfe != nil {
|
|
parentFS.IsPacked = *rfe == descriptorpb.FeatureSet_PACKED
|
|
}
|
|
|
|
if utf8val := child.Utf8Validation; utf8val != nil {
|
|
parentFS.IsUTF8Validated = *utf8val == descriptorpb.FeatureSet_VERIFY
|
|
}
|
|
|
|
if me := child.MessageEncoding; me != nil {
|
|
parentFS.IsDelimitedEncoded = *me == descriptorpb.FeatureSet_DELIMITED
|
|
}
|
|
|
|
if jf := child.JsonFormat; jf != nil {
|
|
parentFS.IsJSONCompliant = *jf == descriptorpb.FeatureSet_ALLOW
|
|
}
|
|
|
|
if goFeatures, ok := proto.GetExtension(child, gofeaturespb.E_Go).(*gofeaturespb.GoFeatures); ok && goFeatures != nil {
|
|
if luje := goFeatures.LegacyUnmarshalJsonEnum; luje != nil {
|
|
parentFS.GenerateLegacyUnmarshalJSON = *luje
|
|
}
|
|
}
|
|
|
|
return parentFS
|
|
}
|
|
|
|
// initFileDescFromFeatureSet initializes editions related fields in fd based
|
|
// on fs. If fs is nil it is assumed to be an empty featureset and all fields
|
|
// will be initialized with the appropriate default. fd.L1.Edition must be set
|
|
// before calling this function.
|
|
func initFileDescFromFeatureSet(fd *filedesc.File, fs *descriptorpb.FeatureSet) {
|
|
dfs := getFeatureSetFor(fd.L1.Edition)
|
|
// initialize the featureset with the defaults
|
|
fd.L1.EditionFeatures = mergeEditionFeatures(fd, dfs)
|
|
// overwrite any options explicitly specified
|
|
fd.L1.EditionFeatures = mergeEditionFeatures(fd, fs)
|
|
}
|