mirror of
https://github.com/Control-D-Inc/ctrld.git
synced 2026-10-12 01:58:46 +02:00
feat(cli): add opt-in macOS unprivileged log-send socket
This commit is contained in:
1 parent
cee5bbe7ae
commit
bfc46ef265
19 files changed
+1686
-7
No files matched your search
@@ -0,0 +1,134 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// logUploadGate is shared by both sockets. Reserve before opening or collecting
|
||||
// anything, and cool down after completion, including failures. Logging reloads
|
||||
// must never reset this state.
|
||||
type logUploadGate struct {
|
||||
mu sync.Mutex
|
||||
busy bool
|
||||
next time.Time
|
||||
}
|
||||
|
||||
func (g *logUploadGate) reserve() bool {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
if g.busy || time.Now().Before(g.next) {
|
||||
return false
|
||||
}
|
||||
g.busy = true
|
||||
return true
|
||||
}
|
||||
|
||||
func (g *logUploadGate) release() {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
g.busy = false
|
||||
g.next = time.Now().Add(logWriterSentInterval)
|
||||
}
|
||||
|
||||
const delegatedSendTimeout = 5 * time.Minute
|
||||
|
||||
type logSendPeerKey struct{}
|
||||
|
||||
// logSendServer has no administrative mux. Its dependencies are immutable
|
||||
// snapshots of daemon state, never request arguments or reloaded config pointers.
|
||||
type logSendServer struct {
|
||||
gate *logUploadGate
|
||||
collect func(context.Context) (io.ReadCloser, error)
|
||||
upload func(context.Context, io.ReadCloser) error
|
||||
audit func(int64, int)
|
||||
server *http.Server
|
||||
cancel context.CancelFunc
|
||||
mu sync.Mutex
|
||||
stopped bool
|
||||
active sync.WaitGroup
|
||||
}
|
||||
|
||||
func (s *logSendServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
uid, known := r.Context().Value(logSendPeerKey{}).(uint32)
|
||||
caller := int64(-1)
|
||||
if known {
|
||||
caller = int64(uid)
|
||||
}
|
||||
code := http.StatusForbidden
|
||||
accepted := false
|
||||
admitted := false
|
||||
defer func() {
|
||||
if admitted {
|
||||
defer s.active.Done()
|
||||
}
|
||||
if accepted && s.audit != nil {
|
||||
s.audit(caller, code)
|
||||
}
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
w.Header().Set("Connection", "close")
|
||||
w.WriteHeader(code) // status only: no logs, credentials, paths or API errors
|
||||
}()
|
||||
if !known {
|
||||
return
|
||||
}
|
||||
if r.Method != http.MethodPost || r.RequestURI != sendLogsPath || r.URL.RawQuery != "" || r.ContentLength != 0 || len(r.TransferEncoding) != 0 || r.Header.Get("Expect") != "" {
|
||||
code = http.StatusBadRequest
|
||||
return
|
||||
}
|
||||
s.mu.Lock()
|
||||
if s.stopped {
|
||||
s.mu.Unlock()
|
||||
code = http.StatusServiceUnavailable
|
||||
return
|
||||
}
|
||||
s.active.Add(1)
|
||||
admitted = true
|
||||
s.mu.Unlock()
|
||||
if !s.gate.reserve() {
|
||||
code = http.StatusServiceUnavailable
|
||||
return
|
||||
}
|
||||
accepted = true
|
||||
defer s.gate.release()
|
||||
ctx, cancel := context.WithTimeout(r.Context(), delegatedSendTimeout)
|
||||
defer cancel()
|
||||
body, err := s.collect(ctx)
|
||||
if err != nil {
|
||||
code = http.StatusPreconditionFailed
|
||||
return
|
||||
}
|
||||
defer body.Close()
|
||||
if err := s.upload(ctx, body); err != nil {
|
||||
code = http.StatusBadGateway
|
||||
return
|
||||
}
|
||||
code = http.StatusOK
|
||||
}
|
||||
|
||||
func (s *logSendServer) stop() {
|
||||
s.mu.Lock()
|
||||
s.stopped = true
|
||||
s.mu.Unlock()
|
||||
if s.cancel != nil {
|
||||
s.cancel()
|
||||
}
|
||||
if s.server != nil {
|
||||
_ = s.server.Close()
|
||||
}
|
||||
s.active.Wait()
|
||||
}
|
||||
|
||||
// The lifecycle lock also covers startup racing with shutdown. Stop fences new
|
||||
// requests, cancels active uploads, and joins them before log writers close.
|
||||
func (p *prog) stopLogSendServer() {
|
||||
p.logSendMu.Lock()
|
||||
defer p.logSendMu.Unlock()
|
||||
p.logSendStopped = true
|
||||
if p.logSend != nil {
|
||||
p.logSend.stop()
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user