 8d9b62daf3
			
		
	
	8d9b62daf3
	
	
	
		
			
			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>
		
			
				
	
	
		
			331 lines
		
	
	
		
			8.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			331 lines
		
	
	
		
			8.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright The OpenTelemetry Authors
 | |
| // SPDX-License-Identifier: Apache-2.0
 | |
| 
 | |
| package trace // import "go.opentelemetry.io/otel/trace"
 | |
| 
 | |
| import (
 | |
| 	"encoding/json"
 | |
| 	"fmt"
 | |
| 	"strings"
 | |
| )
 | |
| 
 | |
| const (
 | |
| 	maxListMembers = 32
 | |
| 
 | |
| 	listDelimiters  = ","
 | |
| 	memberDelimiter = "="
 | |
| 
 | |
| 	errInvalidKey    errorConst = "invalid tracestate key"
 | |
| 	errInvalidValue  errorConst = "invalid tracestate value"
 | |
| 	errInvalidMember errorConst = "invalid tracestate list-member"
 | |
| 	errMemberNumber  errorConst = "too many list-members in tracestate"
 | |
| 	errDuplicate     errorConst = "duplicate list-member in tracestate"
 | |
| )
 | |
| 
 | |
| type member struct {
 | |
| 	Key   string
 | |
| 	Value string
 | |
| }
 | |
| 
 | |
| // according to (chr = %x20 / (nblk-char = %x21-2B / %x2D-3C / %x3E-7E) )
 | |
| // means (chr = %x20-2B / %x2D-3C / %x3E-7E) .
 | |
| func checkValueChar(v byte) bool {
 | |
| 	return v >= '\x20' && v <= '\x7e' && v != '\x2c' && v != '\x3d'
 | |
| }
 | |
| 
 | |
| // according to (nblk-chr = %x21-2B / %x2D-3C / %x3E-7E) .
 | |
| func checkValueLast(v byte) bool {
 | |
| 	return v >= '\x21' && v <= '\x7e' && v != '\x2c' && v != '\x3d'
 | |
| }
 | |
| 
 | |
| // based on the W3C Trace Context specification
 | |
| //
 | |
| //	value    = (0*255(chr)) nblk-chr
 | |
| //	nblk-chr = %x21-2B / %x2D-3C / %x3E-7E
 | |
| //	chr      = %x20 / nblk-chr
 | |
| //
 | |
| // see https://www.w3.org/TR/trace-context-1/#value
 | |
| func checkValue(val string) bool {
 | |
| 	n := len(val)
 | |
| 	if n == 0 || n > 256 {
 | |
| 		return false
 | |
| 	}
 | |
| 	for i := 0; i < n-1; i++ {
 | |
| 		if !checkValueChar(val[i]) {
 | |
| 			return false
 | |
| 		}
 | |
| 	}
 | |
| 	return checkValueLast(val[n-1])
 | |
| }
 | |
| 
 | |
| func checkKeyRemain(key string) bool {
 | |
| 	// ( lcalpha / DIGIT / "_" / "-"/ "*" / "/" )
 | |
| 	for _, v := range key {
 | |
| 		if isAlphaNum(byte(v)) {
 | |
| 			continue
 | |
| 		}
 | |
| 		switch v {
 | |
| 		case '_', '-', '*', '/':
 | |
| 			continue
 | |
| 		}
 | |
| 		return false
 | |
| 	}
 | |
| 	return true
 | |
| }
 | |
| 
 | |
| // according to
 | |
| //
 | |
| //	simple-key = lcalpha (0*255( lcalpha / DIGIT / "_" / "-"/ "*" / "/" ))
 | |
| //	system-id = lcalpha (0*13( lcalpha / DIGIT / "_" / "-"/ "*" / "/" ))
 | |
| //
 | |
| // param n is remain part length, should be 255 in simple-key or 13 in system-id.
 | |
| func checkKeyPart(key string, n int) bool {
 | |
| 	if key == "" {
 | |
| 		return false
 | |
| 	}
 | |
| 	first := key[0] // key's first char
 | |
| 	ret := len(key[1:]) <= n
 | |
| 	ret = ret && first >= 'a' && first <= 'z'
 | |
| 	return ret && checkKeyRemain(key[1:])
 | |
| }
 | |
| 
 | |
| func isAlphaNum(c byte) bool {
 | |
| 	if c >= 'a' && c <= 'z' {
 | |
| 		return true
 | |
| 	}
 | |
| 	return c >= '0' && c <= '9'
 | |
| }
 | |
| 
 | |
| // according to
 | |
| //
 | |
| //	tenant-id = ( lcalpha / DIGIT ) 0*240( lcalpha / DIGIT / "_" / "-"/ "*" / "/" )
 | |
| //
 | |
| // param n is remain part length, should be 240 exactly.
 | |
| func checkKeyTenant(key string, n int) bool {
 | |
| 	if key == "" {
 | |
| 		return false
 | |
| 	}
 | |
| 	return isAlphaNum(key[0]) && len(key[1:]) <= n && checkKeyRemain(key[1:])
 | |
| }
 | |
| 
 | |
| // based on the W3C Trace Context specification
 | |
| //
 | |
| //	key = simple-key / multi-tenant-key
 | |
| //	simple-key = lcalpha (0*255( lcalpha / DIGIT / "_" / "-"/ "*" / "/" ))
 | |
| //	multi-tenant-key = tenant-id "@" system-id
 | |
| //	tenant-id = ( lcalpha / DIGIT ) (0*240( lcalpha / DIGIT / "_" / "-"/ "*" / "/" ))
 | |
| //	system-id = lcalpha (0*13( lcalpha / DIGIT / "_" / "-"/ "*" / "/" ))
 | |
| //	lcalpha    = %x61-7A ; a-z
 | |
| //
 | |
| // see https://www.w3.org/TR/trace-context-1/#tracestate-header.
 | |
| func checkKey(key string) bool {
 | |
| 	tenant, system, ok := strings.Cut(key, "@")
 | |
| 	if !ok {
 | |
| 		return checkKeyPart(key, 255)
 | |
| 	}
 | |
| 	return checkKeyTenant(tenant, 240) && checkKeyPart(system, 13)
 | |
| }
 | |
| 
 | |
| func newMember(key, value string) (member, error) {
 | |
| 	if !checkKey(key) {
 | |
| 		return member{}, errInvalidKey
 | |
| 	}
 | |
| 	if !checkValue(value) {
 | |
| 		return member{}, errInvalidValue
 | |
| 	}
 | |
| 	return member{Key: key, Value: value}, nil
 | |
| }
 | |
| 
 | |
| func parseMember(m string) (member, error) {
 | |
| 	key, val, ok := strings.Cut(m, memberDelimiter)
 | |
| 	if !ok {
 | |
| 		return member{}, fmt.Errorf("%w: %s", errInvalidMember, m)
 | |
| 	}
 | |
| 	key = strings.TrimLeft(key, " \t")
 | |
| 	val = strings.TrimRight(val, " \t")
 | |
| 	result, e := newMember(key, val)
 | |
| 	if e != nil {
 | |
| 		return member{}, fmt.Errorf("%w: %s", errInvalidMember, m)
 | |
| 	}
 | |
| 	return result, nil
 | |
| }
 | |
| 
 | |
| // String encodes member into a string compliant with the W3C Trace Context
 | |
| // specification.
 | |
| func (m member) String() string {
 | |
| 	return m.Key + "=" + m.Value
 | |
| }
 | |
| 
 | |
| // TraceState provides additional vendor-specific trace identification
 | |
| // information across different distributed tracing systems. It represents an
 | |
| // immutable list consisting of key/value pairs, each pair is referred to as a
 | |
| // list-member.
 | |
| //
 | |
| // TraceState conforms to the W3C Trace Context specification
 | |
| // (https://www.w3.org/TR/trace-context-1). All operations that create or copy
 | |
| // a TraceState do so by validating all input and will only produce TraceState
 | |
| // that conform to the specification. Specifically, this means that all
 | |
| // list-member's key/value pairs are valid, no duplicate list-members exist,
 | |
| // and the maximum number of list-members (32) is not exceeded.
 | |
| type TraceState struct { //nolint:revive // revive complains about stutter of `trace.TraceState`
 | |
| 	// list is the members in order.
 | |
| 	list []member
 | |
| }
 | |
| 
 | |
| var _ json.Marshaler = TraceState{}
 | |
| 
 | |
| // ParseTraceState attempts to decode a TraceState from the passed
 | |
| // string. It returns an error if the input is invalid according to the W3C
 | |
| // Trace Context specification.
 | |
| func ParseTraceState(ts string) (TraceState, error) {
 | |
| 	if ts == "" {
 | |
| 		return TraceState{}, nil
 | |
| 	}
 | |
| 
 | |
| 	wrapErr := func(err error) error {
 | |
| 		return fmt.Errorf("failed to parse tracestate: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	var members []member
 | |
| 	found := make(map[string]struct{})
 | |
| 	for ts != "" {
 | |
| 		var memberStr string
 | |
| 		memberStr, ts, _ = strings.Cut(ts, listDelimiters)
 | |
| 		if memberStr == "" {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		m, err := parseMember(memberStr)
 | |
| 		if err != nil {
 | |
| 			return TraceState{}, wrapErr(err)
 | |
| 		}
 | |
| 
 | |
| 		if _, ok := found[m.Key]; ok {
 | |
| 			return TraceState{}, wrapErr(errDuplicate)
 | |
| 		}
 | |
| 		found[m.Key] = struct{}{}
 | |
| 
 | |
| 		members = append(members, m)
 | |
| 		if n := len(members); n > maxListMembers {
 | |
| 			return TraceState{}, wrapErr(errMemberNumber)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return TraceState{list: members}, nil
 | |
| }
 | |
| 
 | |
| // MarshalJSON marshals the TraceState into JSON.
 | |
| func (ts TraceState) MarshalJSON() ([]byte, error) {
 | |
| 	return json.Marshal(ts.String())
 | |
| }
 | |
| 
 | |
| // String encodes the TraceState into a string compliant with the W3C
 | |
| // Trace Context specification. The returned string will be invalid if the
 | |
| // TraceState contains any invalid members.
 | |
| func (ts TraceState) String() string {
 | |
| 	if len(ts.list) == 0 {
 | |
| 		return ""
 | |
| 	}
 | |
| 	var n int
 | |
| 	n += len(ts.list)     // member delimiters: '='
 | |
| 	n += len(ts.list) - 1 // list delimiters: ','
 | |
| 	for _, mem := range ts.list {
 | |
| 		n += len(mem.Key)
 | |
| 		n += len(mem.Value)
 | |
| 	}
 | |
| 
 | |
| 	var sb strings.Builder
 | |
| 	sb.Grow(n)
 | |
| 	_, _ = sb.WriteString(ts.list[0].Key)
 | |
| 	_ = sb.WriteByte('=')
 | |
| 	_, _ = sb.WriteString(ts.list[0].Value)
 | |
| 	for i := 1; i < len(ts.list); i++ {
 | |
| 		_ = sb.WriteByte(listDelimiters[0])
 | |
| 		_, _ = sb.WriteString(ts.list[i].Key)
 | |
| 		_ = sb.WriteByte('=')
 | |
| 		_, _ = sb.WriteString(ts.list[i].Value)
 | |
| 	}
 | |
| 	return sb.String()
 | |
| }
 | |
| 
 | |
| // Get returns the value paired with key from the corresponding TraceState
 | |
| // list-member if it exists, otherwise an empty string is returned.
 | |
| func (ts TraceState) Get(key string) string {
 | |
| 	for _, member := range ts.list {
 | |
| 		if member.Key == key {
 | |
| 			return member.Value
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return ""
 | |
| }
 | |
| 
 | |
| // Walk walks all key value pairs in the TraceState by calling f
 | |
| // Iteration stops if f returns false.
 | |
| func (ts TraceState) Walk(f func(key, value string) bool) {
 | |
| 	for _, m := range ts.list {
 | |
| 		if !f(m.Key, m.Value) {
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Insert adds a new list-member defined by the key/value pair to the
 | |
| // TraceState. If a list-member already exists for the given key, that
 | |
| // list-member's value is updated. The new or updated list-member is always
 | |
| // moved to the beginning of the TraceState as specified by the W3C Trace
 | |
| // Context specification.
 | |
| //
 | |
| // If key or value are invalid according to the W3C Trace Context
 | |
| // specification an error is returned with the original TraceState.
 | |
| //
 | |
| // If adding a new list-member means the TraceState would have more members
 | |
| // then is allowed, the new list-member will be inserted and the right-most
 | |
| // list-member will be dropped in the returned TraceState.
 | |
| func (ts TraceState) Insert(key, value string) (TraceState, error) {
 | |
| 	m, err := newMember(key, value)
 | |
| 	if err != nil {
 | |
| 		return ts, err
 | |
| 	}
 | |
| 	n := len(ts.list)
 | |
| 	found := n
 | |
| 	for i := range ts.list {
 | |
| 		if ts.list[i].Key == key {
 | |
| 			found = i
 | |
| 		}
 | |
| 	}
 | |
| 	cTS := TraceState{}
 | |
| 	if found == n && n < maxListMembers {
 | |
| 		cTS.list = make([]member, n+1)
 | |
| 	} else {
 | |
| 		cTS.list = make([]member, n)
 | |
| 	}
 | |
| 	cTS.list[0] = m
 | |
| 	// When the number of members exceeds capacity, drop the "right-most".
 | |
| 	copy(cTS.list[1:], ts.list[0:found])
 | |
| 	if found < n {
 | |
| 		copy(cTS.list[1+found:], ts.list[found+1:])
 | |
| 	}
 | |
| 	return cTS, nil
 | |
| }
 | |
| 
 | |
| // Delete returns a copy of the TraceState with the list-member identified by
 | |
| // key removed.
 | |
| func (ts TraceState) Delete(key string) TraceState {
 | |
| 	members := make([]member, ts.Len())
 | |
| 	copy(members, ts.list)
 | |
| 	for i, member := range ts.list {
 | |
| 		if member.Key == key {
 | |
| 			members = append(members[:i], members[i+1:]...)
 | |
| 			// TraceState should contain no duplicate members.
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| 	return TraceState{list: members}
 | |
| }
 | |
| 
 | |
| // Len returns the number of list-members in the TraceState.
 | |
| func (ts TraceState) Len() int {
 | |
| 	return len(ts.list)
 | |
| }
 |