package managementaccess import ( "bufio" "bytes" "context" "encoding/json" "errors" "fmt" "io" "net" "time" "tera-clone/services/tera-api/internal/domain" ) const maxMessageBytes = 16 * 1024 type socketRequest struct { Action string `json:"action"` Settings domain.ManagementAccessSettings `json:"settings"` } type socketResponse struct { OK bool `json:"ok"` Code string `json:"code,omitempty"` Error string `json:"error,omitempty"` } type SocketServer struct{ Controller Controller } func (s SocketServer) Serve(listener net.Listener) error { if s.Controller == nil { return errors.New("management access controller is required") } for { connection, err := listener.Accept() if err != nil { if errors.Is(err, net.ErrClosed) { return nil } if temporary, ok := err.(interface{ Temporary() bool }); ok && temporary.Temporary() { continue } return err } go s.handle(connection) } } func (s SocketServer) handle(connection net.Conn) { defer connection.Close() _ = connection.SetDeadline(time.Now().Add(12 * time.Second)) payload, err := bufio.NewReader(io.LimitReader(connection, maxMessageBytes+1)).ReadBytes('\n') if err != nil && !errors.Is(err, io.EOF) { s.respond(connection, socketResponse{Code: "validation_failed", Error: "invalid management access request"}) return } if len(payload) == 0 || len(payload) > maxMessageBytes || payload[len(payload)-1] != '\n' { s.respond(connection, socketResponse{Code: "validation_failed", Error: "invalid management access request"}) return } decoder := json.NewDecoder(bytes.NewReader(payload)) decoder.DisallowUnknownFields() var request socketRequest if err := decoder.Decode(&request); err != nil { s.respond(connection, socketResponse{Code: "validation_failed", Error: "invalid management access request"}) return } var extra any if err := decoder.Decode(&extra); !errors.Is(err, io.EOF) { s.respond(connection, socketResponse{Code: "validation_failed", Error: "management access request must contain one JSON value"}) return } if request.Action != "apply" { s.respond(connection, socketResponse{Code: "validation_failed", Error: "management access action is not supported"}) return } ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() if err := s.Controller.Apply(ctx, request.Settings); err != nil { code := "management_access_failed" if IsUnavailable(err) { code = "management_access_unavailable" } s.respond(connection, socketResponse{Code: code, Error: err.Error()}) return } s.respond(connection, socketResponse{OK: true}) } func (SocketServer) respond(connection net.Conn, response socketResponse) { _ = json.NewEncoder(connection).Encode(response) } type SocketClient struct { path string timeout time.Duration } func NewSocketClient(path string) *SocketClient { if path == "" { path = DefaultSocketPath } return &SocketClient{path: path, timeout: 12 * time.Second} } func (c *SocketClient) Apply(ctx context.Context, settings domain.ManagementAccessSettings) error { if err := Validate(settings); err != nil { return err } dialer := net.Dialer{Timeout: c.timeout} connection, err := dialer.DialContext(ctx, "unix", c.path) if err != nil { return &UnavailableError{Reason: "tera-accessd is not reachable"} } defer connection.Close() deadline := time.Now().Add(c.timeout) if contextDeadline, ok := ctx.Deadline(); ok && contextDeadline.Before(deadline) { deadline = contextDeadline } _ = connection.SetDeadline(deadline) if err := json.NewEncoder(connection).Encode(socketRequest{Action: "apply", Settings: settings}); err != nil { return errors.New("management access request failed") } var response socketResponse if err := json.NewDecoder(io.LimitReader(connection, maxMessageBytes)).Decode(&response); err != nil { return &UnavailableError{Reason: "tera-accessd returned no response"} } if response.Error != "" { if response.Code == "management_access_unavailable" { return &UnavailableError{Reason: response.Error} } return fmt.Errorf("tera-accessd: %s", response.Error) } if !response.OK { return errors.New("tera-accessd rejected the request") } return nil }