diff --git a/audit/multi_auditor.go b/audit/multi_auditor.go new file mode 100644 index 0000000..1157c2d --- /dev/null +++ b/audit/multi_auditor.go @@ -0,0 +1,18 @@ +package audit + +// MultiAuditor wraps multiple auditors and sends audit events to all of them. +type MultiAuditor struct { + auditors []Auditor +} + +// NewMultiAuditor creates a new MultiAuditor that sends to all provided auditors. +func NewMultiAuditor(auditors ...Auditor) *MultiAuditor { + return &MultiAuditor{auditors: auditors} +} + +// AuditRequest sends the request to all wrapped auditors. +func (m *MultiAuditor) AuditRequest(req Request) { + for _, a := range m.auditors { + a.AuditRequest(req) + } +} diff --git a/audit/socket_auditor.go b/audit/socket_auditor.go new file mode 100644 index 0000000..2751ea3 --- /dev/null +++ b/audit/socket_auditor.go @@ -0,0 +1,225 @@ +package audit + +import ( + "context" + "encoding/binary" + "log/slog" + "net" + "time" + + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/timestamppb" + + agentproto "github.com/coder/coder/v2/agent/proto" +) + +const ( + defaultBatchSize = 10 + defaultBatchTimerDuration = 5 * time.Second + // DefaultAuditSocketPath is the well-known path for the boundary audit socket. + // The expectation is the Coder agent listens on this socket to receive audit logs. + DefaultAuditSocketPath = "/tmp/boundary-audit.sock" +) + +// SocketAuditor implements the Auditor interface. It sends logs to the +// workspace agent's boundary log proxy socket. It queues logs and sends +// them in batches using a batch size and timer. The internal queue operates +// as a FIFO i.e., logs are sent in the order they are received and dropped +// if the queue is full. +type SocketAuditor struct { + socketPath string + logger *slog.Logger + logCh chan *agentproto.BoundaryLog + batchSize int + batchTimerDuration time.Duration + + // onFlushAttempt is called after each flush attempt (intended for testing). + onFlushAttempt func() +} + +// NewSocketAuditor creates a new SocketAuditor that sends logs to the agent's +// boundary log proxy socket at DefaultAuditSocketPath after SocketAuditor.Loop +// is called. +func NewSocketAuditor(logger *slog.Logger) *SocketAuditor { + return &SocketAuditor{ + socketPath: DefaultAuditSocketPath, + logger: logger, + logCh: make(chan *agentproto.BoundaryLog, 2*defaultBatchSize), + batchSize: defaultBatchSize, + batchTimerDuration: defaultBatchTimerDuration, + } +} + +// AuditRequest implements the Auditor interface. It queues the log to be sent to the +// agent in a batch. +func (s *SocketAuditor) AuditRequest(req Request) { + httpReq := &agentproto.BoundaryLog_HttpRequest{ + Method: req.Method, + Url: req.URL, + } + // Only include the matched rule for denied requests, as documented in + // the proto schema. + if !req.Allowed { + httpReq.MatchedRule = req.Rule + } + + log := &agentproto.BoundaryLog{ + Allowed: req.Allowed, + Time: timestamppb.Now(), + Resource: &agentproto.BoundaryLog_HttpRequest_{HttpRequest: httpReq}, + } + + select { + case s.logCh <- log: + default: + s.logger.Warn("audit log dropped, channel full") + } +} + +// flushErr represents an error from flush, distinguishing between +// permanent errors (bad data) and transient errors (network issues). +type flushErr struct { + err error + permanent bool +} + +func (e *flushErr) Error() string { return e.err.Error() } + +// flush sends the current batch of logs to the given connection. +func flush(conn net.Conn, logs []*agentproto.BoundaryLog) *flushErr { + if len(logs) == 0 { + return nil + } + + req := &agentproto.ReportBoundaryLogsRequest{ + Logs: logs, + } + + data, err := proto.Marshal(req) + if err != nil { + return &flushErr{err: err, permanent: true} + } + + if err := binary.Write(conn, binary.BigEndian, uint32(len(data))); err != nil { + return &flushErr{err: err} + } + if _, err := conn.Write(data); err != nil { + return &flushErr{err: err} + } + return nil +} + +// Loop handles the I/O to send audit logs to the agent. +func (s *SocketAuditor) Loop(ctx context.Context) { + var conn net.Conn + batch := make([]*agentproto.BoundaryLog, 0, s.batchSize) + t := time.NewTimer(0) + t.Stop() + + // connect attempts to establish a connection to the socket. + connect := func() { + if conn != nil { + return + } + var err error + conn, err = net.Dial("unix", s.socketPath) + if err != nil { + s.logger.Warn("failed to connect to audit socket", "path", s.socketPath, "error", err) + conn = nil + } + } + + // closeConn closes the current connection if open. + closeConn := func() { + if conn != nil { + _ = conn.Close() + conn = nil + } + } + + // clearBatch resets the length of the batch and frees memory while preserving + // the batch slice backing array. + clearBatch := func() { + for i := range len(batch) { + batch[i] = nil + } + batch = batch[:0] + } + + // doFlush flushes the batch and handles errors by reconnecting. + doFlush := func() { + t.Stop() + defer func() { + if s.onFlushAttempt != nil { + s.onFlushAttempt() + } + }() + if len(batch) == 0 { + return + } + connect() + if conn == nil { + // No connection: logs will be retried on next flush. + return + } + + if err := flush(conn, batch); err != nil { + s.logger.Warn("failed to flush audit logs", "error", err) + if err.permanent { + // Data error: discard batch to avoid infinite retries. + clearBatch() + } else { + // Network error: close connection but keep batch for a future retry. + closeConn() + } + return + } + + clearBatch() + } + + connect() + + for { + select { + case <-ctx.Done(): + // Drain any pending logs before the last flush. Not concerned about + // growing the batch slice here since we're exiting. + drain: + for { + select { + case log := <-s.logCh: + batch = append(batch, log) + default: + break drain + } + } + + doFlush() + closeConn() + return + case <-t.C: + doFlush() + case log := <-s.logCh: + // If batch is at capacity, attempt flushing first and drop the log if + // the batch still full. + if len(batch) >= s.batchSize { + doFlush() + if len(batch) >= s.batchSize { + s.logger.Warn("audit log dropped, batch full") + continue + } + } + + batch = append(batch, log) + + if len(batch) == 1 { + t.Reset(s.batchTimerDuration) + } + + if len(batch) >= s.batchSize { + doFlush() + } + } + } +} diff --git a/audit/socket_auditor_test.go b/audit/socket_auditor_test.go new file mode 100644 index 0000000..c120639 --- /dev/null +++ b/audit/socket_auditor_test.go @@ -0,0 +1,411 @@ +package audit + +import ( + "context" + "encoding/binary" + "io" + "log/slog" + "net" + "os" + "path" + "runtime" + "strconv" + "strings" + "sync" + "testing" + "time" + + "google.golang.org/protobuf/proto" + + agentproto "github.com/coder/coder/v2/agent/proto" +) + +func TestSocketAuditor_AuditRequest_QueuesLog(t *testing.T) { + t.Parallel() + + auditor := setupSocketAuditor(t) + + auditor.AuditRequest(Request{ + Method: "GET", + URL: "https://example.com", + Host: "example.com", + Allowed: true, + Rule: "allow-all", + }) + + select { + case log := <-auditor.logCh: + if log.Allowed != true { + t.Errorf("expected Allowed=true, got %v", log.Allowed) + } + httpReq := log.GetHttpRequest() + if httpReq == nil { + t.Fatal("expected HttpRequest, got nil") + } + if httpReq.Method != "GET" { + t.Errorf("expected Method=GET, got %s", httpReq.Method) + } + if httpReq.Url != "https://example.com" { + t.Errorf("expected URL=https://example.com, got %s", httpReq.Url) + } + // Rule should not be set for allowed requests + if httpReq.MatchedRule != "" { + t.Errorf("expected empty MatchedRule for allowed request, got %s", httpReq.MatchedRule) + } + default: + t.Fatal("expected log in channel, got none") + } +} + +func TestSocketAuditor_AuditRequest_DeniedIncludesRule(t *testing.T) { + t.Parallel() + + auditor := setupSocketAuditor(t) + + auditor.AuditRequest(Request{ + Method: "POST", + URL: "https://evil.com", + Host: "evil.com", + Allowed: false, + Rule: "block-evil", + }) + + select { + case log := <-auditor.logCh: + if log.Allowed != false { + t.Errorf("expected Allowed=false, got %v", log.Allowed) + } + httpReq := log.GetHttpRequest() + if httpReq == nil { + t.Fatal("expected HttpRequest, got nil") + } + if httpReq.MatchedRule != "block-evil" { + t.Errorf("expected MatchedRule=block-evil, got %s", httpReq.MatchedRule) + } + default: + t.Fatal("expected log in channel, got none") + } +} + +func TestSocketAuditor_AuditRequest_DropsWhenFull(t *testing.T) { + t.Parallel() + + auditor := setupSocketAuditor(t) + + // Fill the channel (capacity is 2*batchSize = 20) + for i := 0; i < 2*auditor.batchSize; i++ { + auditor.AuditRequest(Request{Method: "GET", URL: "https://example.com", Allowed: true}) + } + + // This should not block and drop the log + auditor.AuditRequest(Request{Method: "GET", URL: "https://dropped.com", Allowed: true}) + + // Drain the channel and verify all entries are from the original batch (dropped.com was dropped) + for i := 0; i < 2*auditor.batchSize; i++ { + v := <-auditor.logCh + resource, ok := v.Resource.(*agentproto.BoundaryLog_HttpRequest_) + if !ok { + t.Fatal("unexpected resource type") + } + if resource.HttpRequest.Url != "https://example.com" { + t.Errorf("expected batch to be FIFO, got %s", resource.HttpRequest.Url) + } + } + + select { + case v := <-auditor.logCh: + t.Errorf("expected empty channel, got %v", v) + default: + } +} + +func TestSocketAuditor_Loop_FlushesOnBatchSize(t *testing.T) { + t.Parallel() + + auditor := setupSocketAuditor(t) + auditor.batchTimerDuration = time.Hour // Ensure timer doesn't interfere with the test + received := make(chan *agentproto.ReportBoundaryLogsRequest, 1) + startTestServer(t, auditor.socketPath, received) + + go auditor.Loop(t.Context()) + + // Send exactly a full batch of logs to trigger a flush + for i := 0; i < auditor.batchSize; i++ { + auditor.AuditRequest(Request{Method: "GET", URL: "https://example.com", Allowed: true}) + } + + select { + case req := <-received: + if len(req.Logs) != auditor.batchSize { + t.Errorf("expected %d logs, got %d", auditor.batchSize, len(req.Logs)) + } + case <-time.After(5 * time.Second): + t.Fatal("timeout waiting for flush") + } +} + +func TestSocketAuditor_Loop_FlushesOnTimer(t *testing.T) { + t.Parallel() + + auditor := setupSocketAuditor(t) + auditor.batchTimerDuration = 3 * time.Second + received := make(chan *agentproto.ReportBoundaryLogsRequest, 1) + startTestServer(t, auditor.socketPath, received) + + go auditor.Loop(t.Context()) + + // A single log should start the timer + auditor.AuditRequest(Request{Method: "GET", URL: "https://example.com", Allowed: true}) + + // Should flush after the timer duration elapses + select { + case req := <-received: + if len(req.Logs) != 1 { + t.Errorf("expected 1 log, got %d", len(req.Logs)) + } + case <-time.After(2 * auditor.batchTimerDuration): + t.Fatal("timeout waiting for timer flush") + } +} + +func TestSocketAuditor_Loop_FlushesOnContextCancel(t *testing.T) { + t.Parallel() + + received := make(chan *agentproto.ReportBoundaryLogsRequest, 1) + + auditor := setupSocketAuditor(t) + // Make the timer long to always exercise the context cancellation case + auditor.batchTimerDuration = time.Hour + startTestServer(t, auditor.socketPath, received) + + ctx, cancel := context.WithCancel(t.Context()) + + var wg sync.WaitGroup + wg.Add(1) + go func() { + defer wg.Done() + auditor.Loop(ctx) + }() + + // Send a log but don't fill the batch + auditor.AuditRequest(Request{Method: "GET", URL: "https://example.com", Allowed: true}) + + cancel() + + select { + case req := <-received: + if len(req.Logs) != 1 { + t.Errorf("expected 1 log, got %d", len(req.Logs)) + } + case <-time.After(5 * time.Second): + t.Fatal("timeout waiting for shutdown flush") + } + + wg.Wait() +} + +func TestSocketAuditor_Loop_RetriesOnConnectionFailure(t *testing.T) { + t.Parallel() + + // Don't start server yet because we want the connection to fail + auditor := setupSocketAuditor(t) + auditor.batchTimerDuration = time.Hour // Ensure timer doesn't interfere with the test + socketPath := auditor.socketPath + + // Set up hook to detect flush attempts + flushed := make(chan struct{}, 1) + auditor.onFlushAttempt = func() { + select { + case flushed <- struct{}{}: + default: + } + } + + go auditor.Loop(t.Context()) + + // Send batchSize+1 logs so we can verify the last log here gets dropped. + for i := 0; i < auditor.batchSize+1; i++ { + url := "https://servernotup" + strconv.Itoa(i) + ".com" + auditor.AuditRequest(Request{Method: "GET", URL: url, Allowed: true}) + } + + // Wait for the first flush attempt before starting the server + select { + case <-flushed: + case <-time.After(5 * time.Second): + t.Fatal("timeout waiting for first flush attempt") + } + + // Now start the server + received := make(chan *agentproto.ReportBoundaryLogsRequest, 1) + startTestServer(t, socketPath, received) + + // Send one more log - batch is at capacity, so this triggers flush first + // The flush succeeds (server is up), sending the retained batch. + auditor.AuditRequest(Request{Method: "POST", URL: "https://serverup.com", Allowed: true}) + + // Should receive the retained batch (the new log goes into a fresh batch) + select { + case req := <-received: + if len(req.Logs) != auditor.batchSize { + t.Errorf("expected %d logs from retry, got %d", auditor.batchSize, len(req.Logs)) + } + for i, log := range req.Logs { + resource, ok := log.Resource.(*agentproto.BoundaryLog_HttpRequest_) + if !ok { + t.Fatal("unexpected resource type") + } + expected := "https://servernotup" + strconv.Itoa(i) + ".com" + if resource.HttpRequest.Url != expected { + t.Errorf("expected log %d URL %s got %v", i, expected, resource.HttpRequest.Url) + } + } + case <-time.After(5 * time.Second): + t.Fatal("timeout waiting for retry flush") + } + + // Trigger another batch and verify contents + for i := 0; i < auditor.batchSize-1; i++ { + url := "https://secondbatch" + strconv.Itoa(i) + ".com" + auditor.AuditRequest(Request{Method: "GET", URL: url, Allowed: true}) + } + + select { + case req := <-received: + if len(req.Logs) != auditor.batchSize { + t.Errorf("expected %d logs from retry, got %d", auditor.batchSize, len(req.Logs)) + } + for i, log := range req.Logs { + resource, ok := log.Resource.(*agentproto.BoundaryLog_HttpRequest_) + if !ok { + t.Fatal("unexpected resource type") + } + expected := "https://secondbatch" + strconv.Itoa(i-1) + ".com" + if i == 0 { + expected = "https://serverup.com" + } + if resource.HttpRequest.Url != expected { + t.Errorf("expected log %d URL %s got %v", i, expected, resource.HttpRequest.Url) + } + } + case <-time.After(5 * time.Second): + t.Fatal("timeout waiting for flush") + } +} + +func TestFlush_EmptyBatch(t *testing.T) { + t.Parallel() + + err := flush(nil, nil) + if err != nil { + t.Errorf("expected nil error for empty batch, got %v", err) + } + + err = flush(nil, []*agentproto.BoundaryLog{}) + if err != nil { + t.Errorf("expected nil error for empty slice, got %v", err) + } +} + +// tempDirUnixSocket returns a temporary directory that can safely hold unix +// sockets (probably). +// +// During tests on darwin we hit the max path length limit for unix sockets +// pretty easily in the default location, so this function uses /tmp instead to +// get shorter paths. +func tempDirUnixSocket(t *testing.T) string { + t.Helper() + if runtime.GOOS == "darwin" { + testName := strings.ReplaceAll(t.Name(), "/", "_") + dir, err := os.MkdirTemp("/tmp", testName) + if err != nil { + t.Errorf("failed to create temp dir: %v", err) + } + + t.Cleanup(func() { + err := os.RemoveAll(dir) + if err != nil { + t.Fatalf("remove temp dir %s: %v", dir, err) + } + }) + return dir + } + + return t.TempDir() +} + +func setupSocketAuditor(t *testing.T) *SocketAuditor { + socketPath := path.Join(tempDirUnixSocket(t), "server.sock") + logger := slog.New(slog.NewTextHandler(io.Discard, nil)) + return &SocketAuditor{ + socketPath: socketPath, + logger: logger, + logCh: make(chan *agentproto.BoundaryLog, 2*defaultBatchSize), + batchSize: defaultBatchSize, + batchTimerDuration: defaultBatchTimerDuration, + } +} + +// startTestServer starts a Unix socket server that reads length-prefixed protobuf messages, +// and reports all received requests to the given channel. +func startTestServer(t *testing.T, socketPath string, received chan<- *agentproto.ReportBoundaryLogsRequest) { + t.Helper() + + listener, err := net.Listen("unix", socketPath) + if err != nil { + t.Fatalf("failed to listen on socket: %v", err) + } + + var wg sync.WaitGroup + t.Cleanup(func() { + _ = listener.Close() + wg.Wait() + }) + + wg.Add(1) + go func() { + defer wg.Done() + + for { + conn, err := listener.Accept() + if err != nil { + return + } + + wg.Add(1) + go handleConn(t, conn, &wg, received) + } + }() +} + +func handleConn(t *testing.T, c net.Conn, wg *sync.WaitGroup, received chan<- *agentproto.ReportBoundaryLogsRequest) { + t.Helper() + defer wg.Done() + defer func() { _ = c.Close() }() + + for { + var length uint32 + if err := binary.Read(c, binary.BigEndian, &length); err != nil { + return + } + + if length > 1<<15 { + t.Errorf("invalid length: %d", length) + return + } + + data := make([]byte, length) + if _, err := io.ReadFull(c, data); err != nil { + t.Errorf("failed to read: %v", err) + return + } + + var req agentproto.ReportBoundaryLogsRequest + if err := proto.Unmarshal(data, &req); err != nil { + t.Errorf("failed to unmarshal: %v", err) + return + } + + received <- &req + } +} diff --git a/go.mod b/go.mod index 7923878..e57cb10 100644 --- a/go.mod +++ b/go.mod @@ -1,39 +1,42 @@ module github.com/coder/boundary -go 1.24.0 +go 1.24.10 require ( github.com/cenkalti/backoff/v5 v5.0.3 - github.com/coder/serpent v0.10.0 + github.com/coder/coder/v2 v2.10.1-0.20251217224952-9635d1c6a692 + github.com/coder/serpent v0.12.0 github.com/landlock-lsm/go-landlock v0.0.0-20251103212306-430f8e5cd97c - github.com/stretchr/testify v1.8.4 - golang.org/x/sys v0.38.0 + github.com/stretchr/testify v1.11.1 + golang.org/x/sys v0.39.0 + google.golang.org/protobuf v1.36.10 ) require ( - cdr.dev/slog v1.6.2-0.20240126064726-20367d4aede6 // indirect + cdr.dev/slog v1.6.2-0.20251120224544-40ff19937ff2 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/coder/pretty v0.0.0-20230908205945-e89ba86370e0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/kr/text v0.2.0 // indirect - github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/lucasb-eyer/go-colorful v1.3.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-runewidth v0.0.15 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect - github.com/muesli/termenv v0.15.2 // indirect - github.com/pion/transport/v2 v2.0.0 // indirect + github.com/muesli/termenv v0.16.0 // indirect + github.com/pion/transport/v2 v2.2.10 // indirect github.com/pion/udp v0.1.4 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/rivo/uniseg v0.4.4 // indirect - github.com/spf13/pflag v1.0.5 // indirect - go.opentelemetry.io/otel v1.19.0 // indirect - go.opentelemetry.io/otel/trace v1.19.0 // indirect - golang.org/x/crypto v0.45.0 // indirect - golang.org/x/exp v0.0.0-20240213143201-ec583247a57a // indirect - golang.org/x/term v0.37.0 // indirect - golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/spf13/pflag v1.0.10 // indirect + github.com/xhit/go-str2duration/v2 v2.1.0 // indirect + github.com/zeebo/errs v1.4.0 // indirect + go.opentelemetry.io/otel v1.38.0 // indirect + go.opentelemetry.io/otel/trace v1.38.0 // indirect + golang.org/x/crypto v0.46.0 // indirect + golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect + golang.org/x/term v0.38.0 // indirect + golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect gopkg.in/yaml.v3 v3.0.1 // indirect kernel.org/pub/linux/libs/security/libcap/psx v1.2.77 // indirect + storj.io/drpc v0.0.33 // indirect ) diff --git a/go.sum b/go.sum index 5de50b1..4bbe5bc 100644 --- a/go.sum +++ b/go.sum @@ -1,36 +1,45 @@ -cdr.dev/slog v1.6.2-0.20240126064726-20367d4aede6 h1:KHblWIE/KHOwQ6lEbMZt6YpcGve2FEZ1sDtrW1Am5UI= -cdr.dev/slog v1.6.2-0.20240126064726-20367d4aede6/go.mod h1:NaoTA7KwopCrnaSb0JXTC0PTp/O/Y83Lndnq0OEV3ZQ= +cdr.dev/slog v1.6.2-0.20251120224544-40ff19937ff2 h1:M4Z9eTbnHPdZI4GpBUNCae0lSgUucY+aW5j7+zB8lCk= +cdr.dev/slog v1.6.2-0.20251120224544-40ff19937ff2/go.mod h1:NaoTA7KwopCrnaSb0JXTC0PTp/O/Y83Lndnq0OEV3ZQ= +cloud.google.com/go v0.121.4 h1:cVvUiY0sX0xwyxPwdSU2KsF9knOVmtRyAMt8xou0iTs= cloud.google.com/go/compute v1.23.0 h1:tP41Zoavr8ptEqaW6j+LQOnyBBhO7OkOMAGrgLopTwY= -cloud.google.com/go/compute v1.23.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= -cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= -cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= -cloud.google.com/go/logging v1.8.1 h1:26skQWPeYhvIasWKm48+Eq7oUqdcdbwsCVwz5Ys0FvU= -cloud.google.com/go/logging v1.8.1/go.mod h1:TJjR+SimHwuC8MZ9cjByQulAMgni+RkXeI3wwctHJEI= -cloud.google.com/go/longrunning v0.5.1 h1:Fr7TXftcqTudoyRJa113hyaqlGdiBQkp0Gq7tErFDWI= -cloud.google.com/go/longrunning v0.5.1/go.mod h1:spvimkwdz6SPWKEt/XBij79E9fiTkHSQl/fRUUQJYJc= +cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= +cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= +cloud.google.com/go/logging v1.13.0 h1:7j0HgAp0B94o1YRDqiqm26w4q1rDMH7XNRU34lJXHYc= +cloud.google.com/go/logging v1.13.0/go.mod h1:36CoKh6KA/M0PbhPKMq6/qety2DCAErbhXT62TuXALA= +cloud.google.com/go/longrunning v0.6.7 h1:IGtfDWHhQCgCjwQjV9iiLnUta9LBCo8R9QmAFsS/PrE= +cloud.google.com/go/longrunning v0.6.7/go.mod h1:EAFV3IZAKmM56TyiE6VAP3VoTzhZzySwI/YI1s/nRsY= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= -github.com/charmbracelet/lipgloss v0.8.0 h1:IS00fk4XAHcf8uZKc3eHeMUTCxUH6NkaTrdyCQk84RU= -github.com/charmbracelet/lipgloss v0.8.0/go.mod h1:p4eYUZZJ/0oXTuCQKFF8mqyKCz0ja6y+7DniDDw5KKU= +github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs= +github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk= +github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 h1:ZR7e0ro+SZZiIZD7msJyA+NjkCNNavuiPBLgerbOziE= +github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834/go.mod h1:aKC/t2arECF6rNOnaKaVU6y4t4ZeHQzqfxedE/VkVhA= +github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE= +github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q= +github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k= +github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= +github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= +github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= +github.com/coder/coder/v2 v2.10.1-0.20251217224952-9635d1c6a692 h1:hZC+lLHELj+KddalTdd1Cx+4YVt7CDnXoTRIriWmYPA= +github.com/coder/coder/v2 v2.10.1-0.20251217224952-9635d1c6a692/go.mod h1:zTCbfqIUeAmiUIwTQpWcXpk6gkwX/hWFKlsp1f93pMg= github.com/coder/pretty v0.0.0-20230908205945-e89ba86370e0 h1:3A0ES21Ke+FxEM8CXx9n47SZOKOpgSE1bbJzlE4qPVs= github.com/coder/pretty v0.0.0-20230908205945-e89ba86370e0/go.mod h1:5UuS2Ts+nTToAMeOjNlnHFkPahrtDkmpydBen/3wgZc= -github.com/coder/serpent v0.10.0 h1:ofVk9FJXSek+SmL3yVE3GoArP83M+1tX+H7S4t8BSuM= -github.com/coder/serpent v0.10.0/go.mod h1:cZFW6/fP+kE9nd/oRkEHJpG6sXCtQ+AX7WMMEHv0Y3Q= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/coder/serpent v0.12.0 h1:fUu3qVjeRvVy3DB/C2EFFvOctm+f2HKyckyfA86O63Q= +github.com/coder/serpent v0.12.0/go.mod h1:mPEpD8Cq106E0glBs5ROAAGoALLtD5HAAMVZmjf4zO0= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= -github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -42,69 +51,87 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/landlock-lsm/go-landlock v0.0.0-20251103212306-430f8e5cd97c h1:QcKqiunpt7hooa/xIx0iyepA6Cs2BgKexaYOxHvHNCs= github.com/landlock-lsm/go-landlock v0.0.0-20251103212306-430f8e5cd97c/go.mod h1:stwyhp9tfeEy3A4bRJLdOEvjW/CetRJg/vcijNG8M5A= -github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= -github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag= +github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= -github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= -github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= -github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= -github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= -github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= +github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= +github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= github.com/natefinch/atomic v1.0.1 h1:ZPYKxkqQOx3KZ+RsbnP/YsgvxWQPGxjC0oBt2AhwV0A= github.com/natefinch/atomic v1.0.1/go.mod h1:N/D/ELrljoqDyT3rZrsUmtsuzvHkeB/wWjHV22AZRbM= github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= -github.com/pion/transport/v2 v2.0.0 h1:bsMYyqHCbkvHwj+eNCFBuxtlKndKfyGI2vaQmM3fIE4= github.com/pion/transport/v2 v2.0.0/go.mod h1:HS2MEBJTwD+1ZI2eSXSvHJx/HnzQqRy2/LXxt6eVMHc= +github.com/pion/transport/v2 v2.2.10 h1:ucLBLE8nuxiHfvkFKnkDQRYWYfp8ejf4YBOPfaQpw6Q= +github.com/pion/transport/v2 v2.2.10/go.mod h1:sq1kSLWs+cHW9E+2fJP95QudkzbK7wscs8yYgQToO5E= +github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0= +github.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uPLGhhz9rwo= github.com/pion/udp v0.1.4 h1:OowsTmu1Od3sD6i3fQUJxJn2fEvJO6L1TidgadtbTI8= github.com/pion/udp v0.1.4/go.mod h1:G8LDo56HsFwC24LIcnT4YIDU5qcB6NepqqjP0keL2us= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= -github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/wlynxg/anet v0.0.3/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA= +github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc= +github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs= -go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY= -go.opentelemetry.io/otel/metric v1.19.0 h1:aTzpGtV0ar9wlV4Sna9sdJyII5jTVJEvKETPiOKwvpE= -go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8= -go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o= -go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A= -go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg= -go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo= +github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= +github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= +github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM= +github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= +go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= +go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= +go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= +go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= +go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= +go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= -golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= -golang.org/x/exp v0.0.0-20240213143201-ec583247a57a h1:HinSgX1tJRX3KsL//Gxynpw5CTOAIPhgL4W8PNiIpVE= -golang.org/x/exp v0.0.0-20240213143201-ec583247a57a/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= +golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= +golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= +golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY= +golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= +golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= +golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -112,36 +139,46 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= -golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= +golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= +golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= +golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= -golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= -google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 h1:wpZ8pe2x1Q3f2KyT5f8oP/fa9rHAKgFPr/HZdNuS+PQ= -google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:J7XzRzVy1+IPwWHZUzoD0IccYZIrXILAQpc+Qy9CMhY= -google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 h1:JpwMPBpFN3uKhdaekDpiNlImDdkUAyiJ6ez/uxGaUSo= -google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:0xJLfVdJqpAPl8tDg1ujOCGzx6LFLttXT5NhllGOXY4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f h1:ultW7fxlIvee4HYrtnaRPon9HpEgFk5zYpmfMgtKB5I= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f/go.mod h1:L9KNLi232K1/xB6f7AlSX692koaRnKaWSR0stBki0Yc= -google.golang.org/grpc v1.61.0 h1:TOvOcuXn30kRao+gfcvsebNEa5iZIiLkisYEkf7R7o0= -google.golang.org/grpc v1.61.0/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= -google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= -google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY= +golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= +google.golang.org/genproto v0.0.0-20250715232539-7130f93afb79 h1:Nt6z9UHqSlIdIGJdz6KhTIs2VRx/iOsA5iE8bmQNcxs= +google.golang.org/genproto v0.0.0-20250715232539-7130f93afb79/go.mod h1:kTmlBHMPqR5uCZPBvwa2B18mvubkjyY3CRLI0c6fj0s= +google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 h1:mepRgnBZa07I4TRuomDE4sTIYieg/osKmzIf4USdWS4= +google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8/go.mod h1:fDMmzKV90WSg1NbozdqrE64fkuTv6mlq2zxo9ad+3yo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846 h1:Wgl1rcDNThT+Zn47YyCXOXyX/COgMTIdhJ717F0l4xk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= +google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= @@ -150,3 +187,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= kernel.org/pub/linux/libs/security/libcap/psx v1.2.77 h1:Z06sMOzc0GNCwp6efaVrIrz4ywGJ1v+DP0pjVkOfDuA= kernel.org/pub/linux/libs/security/libcap/psx v1.2.77/go.mod h1:+l6Ee2F59XiJ2I6WR5ObpC1utCQJZ/VLsEbQCD8RG24= +storj.io/drpc v0.0.33 h1:yCGZ26r66ZdMP0IcTYsj7WDAUIIjzXk6DJhbhvt9FHI= +storj.io/drpc v0.0.33/go.mod h1:vR804UNzhBa49NOJ6HeLjd2H3MakC1j5Gv8bsOQT6N4= diff --git a/landjail/parent.go b/landjail/parent.go index 1f67e14..2d76357 100644 --- a/landjail/parent.go +++ b/landjail/parent.go @@ -26,8 +26,11 @@ func RunParent(ctx context.Context, logger *slog.Logger, config config.AppConfig // Create rule engine ruleEngine := rulesengine.NewRuleEngine(allowRules, logger) - // Create auditor - auditor := audit.NewLogAuditor(logger) + // Create auditors + stderrAuditor := audit.NewLogAuditor(logger) + socketAuditor := audit.NewSocketAuditor(logger) + go socketAuditor.Loop(ctx) + auditor := audit.NewMultiAuditor(stderrAuditor, socketAuditor) // Create TLS certificate manager certManager, err := tls.NewCertificateManager(tls.Config{