 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>
		
			
				
	
	
		
			239 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			239 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright The OpenTelemetry Authors
 | |
| // SPDX-License-Identifier: Apache-2.0
 | |
| 
 | |
| package otelhttp // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
 | |
| 
 | |
| import (
 | |
| 	"net/http"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/felixge/httpsnoop"
 | |
| 	"go.opentelemetry.io/otel"
 | |
| 	"go.opentelemetry.io/otel/attribute"
 | |
| 	"go.opentelemetry.io/otel/propagation"
 | |
| 	"go.opentelemetry.io/otel/trace"
 | |
| 
 | |
| 	"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/request"
 | |
| 	"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv"
 | |
| )
 | |
| 
 | |
| // middleware is an http middleware which wraps the next handler in a span.
 | |
| type middleware struct {
 | |
| 	operation string
 | |
| 	server    string
 | |
| 
 | |
| 	tracer             trace.Tracer
 | |
| 	propagators        propagation.TextMapPropagator
 | |
| 	spanStartOptions   []trace.SpanStartOption
 | |
| 	readEvent          bool
 | |
| 	writeEvent         bool
 | |
| 	filters            []Filter
 | |
| 	spanNameFormatter  func(string, *http.Request) string
 | |
| 	publicEndpoint     bool
 | |
| 	publicEndpointFn   func(*http.Request) bool
 | |
| 	metricAttributesFn func(*http.Request) []attribute.KeyValue
 | |
| 
 | |
| 	semconv semconv.HTTPServer
 | |
| }
 | |
| 
 | |
| func defaultHandlerFormatter(operation string, _ *http.Request) string {
 | |
| 	return operation
 | |
| }
 | |
| 
 | |
| // NewHandler wraps the passed handler in a span named after the operation and
 | |
| // enriches it with metrics.
 | |
| func NewHandler(handler http.Handler, operation string, opts ...Option) http.Handler {
 | |
| 	return NewMiddleware(operation, opts...)(handler)
 | |
| }
 | |
| 
 | |
| // NewMiddleware returns a tracing and metrics instrumentation middleware.
 | |
| // The handler returned by the middleware wraps a handler
 | |
| // in a span named after the operation and enriches it with metrics.
 | |
| func NewMiddleware(operation string, opts ...Option) func(http.Handler) http.Handler {
 | |
| 	h := middleware{
 | |
| 		operation: operation,
 | |
| 	}
 | |
| 
 | |
| 	defaultOpts := []Option{
 | |
| 		WithSpanOptions(trace.WithSpanKind(trace.SpanKindServer)),
 | |
| 		WithSpanNameFormatter(defaultHandlerFormatter),
 | |
| 	}
 | |
| 
 | |
| 	c := newConfig(append(defaultOpts, opts...)...)
 | |
| 	h.configure(c)
 | |
| 
 | |
| 	return func(next http.Handler) http.Handler {
 | |
| 		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 | |
| 			h.serveHTTP(w, r, next)
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (h *middleware) configure(c *config) {
 | |
| 	h.tracer = c.Tracer
 | |
| 	h.propagators = c.Propagators
 | |
| 	h.spanStartOptions = c.SpanStartOptions
 | |
| 	h.readEvent = c.ReadEvent
 | |
| 	h.writeEvent = c.WriteEvent
 | |
| 	h.filters = c.Filters
 | |
| 	h.spanNameFormatter = c.SpanNameFormatter
 | |
| 	h.publicEndpoint = c.PublicEndpoint
 | |
| 	h.publicEndpointFn = c.PublicEndpointFn
 | |
| 	h.server = c.ServerName
 | |
| 	h.semconv = semconv.NewHTTPServer(c.Meter)
 | |
| 	h.metricAttributesFn = c.MetricAttributesFn
 | |
| }
 | |
| 
 | |
| // serveHTTP sets up tracing and calls the given next http.Handler with the span
 | |
| // context injected into the request context.
 | |
| func (h *middleware) serveHTTP(w http.ResponseWriter, r *http.Request, next http.Handler) {
 | |
| 	requestStartTime := time.Now()
 | |
| 	for _, f := range h.filters {
 | |
| 		if !f(r) {
 | |
| 			// Simply pass through to the handler if a filter rejects the request
 | |
| 			next.ServeHTTP(w, r)
 | |
| 			return
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	ctx := h.propagators.Extract(r.Context(), propagation.HeaderCarrier(r.Header))
 | |
| 	opts := []trace.SpanStartOption{
 | |
| 		trace.WithAttributes(h.semconv.RequestTraceAttrs(h.server, r, semconv.RequestTraceAttrsOpts{})...),
 | |
| 	}
 | |
| 
 | |
| 	opts = append(opts, h.spanStartOptions...)
 | |
| 	if h.publicEndpoint || (h.publicEndpointFn != nil && h.publicEndpointFn(r.WithContext(ctx))) {
 | |
| 		opts = append(opts, trace.WithNewRoot())
 | |
| 		// Linking incoming span context if any for public endpoint.
 | |
| 		if s := trace.SpanContextFromContext(ctx); s.IsValid() && s.IsRemote() {
 | |
| 			opts = append(opts, trace.WithLinks(trace.Link{SpanContext: s}))
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	tracer := h.tracer
 | |
| 
 | |
| 	if tracer == nil {
 | |
| 		if span := trace.SpanFromContext(r.Context()); span.SpanContext().IsValid() {
 | |
| 			tracer = newTracer(span.TracerProvider())
 | |
| 		} else {
 | |
| 			tracer = newTracer(otel.GetTracerProvider())
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if startTime := StartTimeFromContext(ctx); !startTime.IsZero() {
 | |
| 		opts = append(opts, trace.WithTimestamp(startTime))
 | |
| 		requestStartTime = startTime
 | |
| 	}
 | |
| 
 | |
| 	ctx, span := tracer.Start(ctx, h.spanNameFormatter(h.operation, r), opts...)
 | |
| 	defer span.End()
 | |
| 
 | |
| 	readRecordFunc := func(int64) {}
 | |
| 	if h.readEvent {
 | |
| 		readRecordFunc = func(n int64) {
 | |
| 			span.AddEvent("read", trace.WithAttributes(ReadBytesKey.Int64(n)))
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// if request body is nil or NoBody, we don't want to mutate the body as it
 | |
| 	// will affect the identity of it in an unforeseeable way because we assert
 | |
| 	// ReadCloser fulfills a certain interface and it is indeed nil or NoBody.
 | |
| 	bw := request.NewBodyWrapper(r.Body, readRecordFunc)
 | |
| 	if r.Body != nil && r.Body != http.NoBody {
 | |
| 		r.Body = bw
 | |
| 	}
 | |
| 
 | |
| 	writeRecordFunc := func(int64) {}
 | |
| 	if h.writeEvent {
 | |
| 		writeRecordFunc = func(n int64) {
 | |
| 			span.AddEvent("write", trace.WithAttributes(WroteBytesKey.Int64(n)))
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	rww := request.NewRespWriterWrapper(w, writeRecordFunc)
 | |
| 
 | |
| 	// Wrap w to use our ResponseWriter methods while also exposing
 | |
| 	// other interfaces that w may implement (http.CloseNotifier,
 | |
| 	// http.Flusher, http.Hijacker, http.Pusher, io.ReaderFrom).
 | |
| 
 | |
| 	w = httpsnoop.Wrap(w, httpsnoop.Hooks{
 | |
| 		Header: func(httpsnoop.HeaderFunc) httpsnoop.HeaderFunc {
 | |
| 			return rww.Header
 | |
| 		},
 | |
| 		Write: func(httpsnoop.WriteFunc) httpsnoop.WriteFunc {
 | |
| 			return rww.Write
 | |
| 		},
 | |
| 		WriteHeader: func(httpsnoop.WriteHeaderFunc) httpsnoop.WriteHeaderFunc {
 | |
| 			return rww.WriteHeader
 | |
| 		},
 | |
| 		Flush: func(httpsnoop.FlushFunc) httpsnoop.FlushFunc {
 | |
| 			return rww.Flush
 | |
| 		},
 | |
| 	})
 | |
| 
 | |
| 	labeler, found := LabelerFromContext(ctx)
 | |
| 	if !found {
 | |
| 		ctx = ContextWithLabeler(ctx, labeler)
 | |
| 	}
 | |
| 
 | |
| 	r = r.WithContext(ctx)
 | |
| 	next.ServeHTTP(w, r)
 | |
| 
 | |
| 	if r.Pattern != "" {
 | |
| 		span.SetName(h.spanNameFormatter(h.operation, r))
 | |
| 	}
 | |
| 
 | |
| 	statusCode := rww.StatusCode()
 | |
| 	bytesWritten := rww.BytesWritten()
 | |
| 	span.SetStatus(h.semconv.Status(statusCode))
 | |
| 	span.SetAttributes(h.semconv.ResponseTraceAttrs(semconv.ResponseTelemetry{
 | |
| 		StatusCode: statusCode,
 | |
| 		ReadBytes:  bw.BytesRead(),
 | |
| 		ReadError:  bw.Error(),
 | |
| 		WriteBytes: bytesWritten,
 | |
| 		WriteError: rww.Error(),
 | |
| 	})...)
 | |
| 
 | |
| 	// Use floating point division here for higher precision (instead of Millisecond method).
 | |
| 	elapsedTime := float64(time.Since(requestStartTime)) / float64(time.Millisecond)
 | |
| 
 | |
| 	metricAttributes := semconv.MetricAttributes{
 | |
| 		Req:                  r,
 | |
| 		StatusCode:           statusCode,
 | |
| 		AdditionalAttributes: append(labeler.Get(), h.metricAttributesFromRequest(r)...),
 | |
| 	}
 | |
| 
 | |
| 	h.semconv.RecordMetrics(ctx, semconv.ServerMetricData{
 | |
| 		ServerName:       h.server,
 | |
| 		ResponseSize:     bytesWritten,
 | |
| 		MetricAttributes: metricAttributes,
 | |
| 		MetricData: semconv.MetricData{
 | |
| 			RequestSize: bw.BytesRead(),
 | |
| 			ElapsedTime: elapsedTime,
 | |
| 		},
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func (h *middleware) metricAttributesFromRequest(r *http.Request) []attribute.KeyValue {
 | |
| 	var attributeForRequest []attribute.KeyValue
 | |
| 	if h.metricAttributesFn != nil {
 | |
| 		attributeForRequest = h.metricAttributesFn(r)
 | |
| 	}
 | |
| 	return attributeForRequest
 | |
| }
 | |
| 
 | |
| // WithRouteTag annotates spans and metrics with the provided route name
 | |
| // with HTTP route attribute.
 | |
| func WithRouteTag(route string, h http.Handler) http.Handler {
 | |
| 	attr := semconv.NewHTTPServer(nil).Route(route)
 | |
| 	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 | |
| 		span := trace.SpanFromContext(r.Context())
 | |
| 		span.SetAttributes(attr)
 | |
| 
 | |
| 		labeler, _ := LabelerFromContext(r.Context())
 | |
| 		labeler.Add(attr)
 | |
| 
 | |
| 		h.ServeHTTP(w, r)
 | |
| 	})
 | |
| }
 |