feat: validate Worker protocol and verify process crash recovery
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -142,9 +143,9 @@ func (h *WorkerHub) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var hello workerFrame
|
||||
hello, decodeErr := decodeWorkerRequest(raw, true)
|
||||
var input worker.ConnectInput
|
||||
if kind != websocket.MessageText || decodeWorkerJSON(raw, &hello) != nil || hello.Version != "1" || hello.Type != "worker.hello" || hello.MessageID.Validate() != nil || hello.SessionID != "" || hello.ReplyTo != "" || hello.Error != nil || decodeWorkerJSON(hello.Body, &input) != nil {
|
||||
if kind != websocket.MessageText || decodeErr != nil || decodeWorkerJSON(hello.Body, &input) != nil {
|
||||
_ = conn.Close(websocket.StatusPolicyViolation, "invalid worker hello")
|
||||
return
|
||||
}
|
||||
@@ -177,8 +178,8 @@ func (h *WorkerHub) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var frame workerFrame
|
||||
if kind != websocket.MessageText || decodeWorkerJSON(raw, &frame) != nil || frame.Version != "1" || frame.MessageID.Validate() != nil || frame.SessionID != binding.SessionID || frame.ReplyTo != "" || frame.Error != nil {
|
||||
frame, decodeErr := decodeWorkerRequest(raw, false)
|
||||
if kind != websocket.MessageText || decodeErr != nil || frame.SessionID != binding.SessionID {
|
||||
_ = conn.Close(websocket.StatusPolicyViolation, "invalid worker frame")
|
||||
return
|
||||
}
|
||||
@@ -393,6 +394,9 @@ func decodeWorkerJSON(raw []byte, out any) error {
|
||||
if err := exactFields(raw, reflect.TypeOf(out).Elem()); err != nil {
|
||||
return fault.Invalid("unknown or invalid worker field")
|
||||
}
|
||||
if err := requiredWorkerFields(raw, reflect.TypeOf(out).Elem()); err != nil {
|
||||
return err
|
||||
}
|
||||
decoder := json.NewDecoder(bytes.NewReader(raw))
|
||||
decoder.DisallowUnknownFields()
|
||||
if err := decoder.Decode(out); err != nil {
|
||||
@@ -400,3 +404,89 @@ func decodeWorkerJSON(raw []byte, out any) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// decodeWorkerRequest 拒绝请求中伪装为响应的字段;空字符串/null 不能绕过方向约束。
|
||||
func decodeWorkerRequest(raw []byte, hello bool) (workerFrame, error) {
|
||||
var frame workerFrame
|
||||
if err := decodeWorkerJSON(raw, &frame); err != nil {
|
||||
return frame, err
|
||||
}
|
||||
if frame.Version != "1" || frame.MessageID.Validate() != nil || len(frame.Body) == 0 {
|
||||
return frame, fault.Invalid("invalid worker envelope")
|
||||
}
|
||||
var fields map[string]json.RawMessage
|
||||
_ = json.Unmarshal(raw, &fields)
|
||||
if fields["error"] != nil || fields["reply_to"] != nil {
|
||||
return frame, fault.Invalid("response fields are not allowed in worker requests")
|
||||
}
|
||||
if hello {
|
||||
if frame.Type != "worker.hello" || fields["session_id"] != nil {
|
||||
return frame, fault.Invalid("invalid worker hello envelope")
|
||||
}
|
||||
} else if frame.SessionID.Validate() != nil {
|
||||
return frame, fault.Invalid("worker session is required")
|
||||
}
|
||||
return frame, nil
|
||||
}
|
||||
|
||||
// requiredWorkerFields 校验必填字段及规范 uint64 字符串,不把省略布尔值等同于显式控制意图。
|
||||
func requiredWorkerFields(raw []byte, t reflect.Type) error {
|
||||
if t == reflect.TypeOf(json.RawMessage{}) || t == reflect.TypeOf(time.Time{}) {
|
||||
return nil
|
||||
}
|
||||
if t.Kind() == reflect.Pointer {
|
||||
return nil
|
||||
}
|
||||
if t.Kind() == reflect.Array || t.Kind() == reflect.Slice {
|
||||
var values []json.RawMessage
|
||||
if err := json.Unmarshal(raw, &values); err != nil {
|
||||
return fault.Invalid("invalid worker array")
|
||||
}
|
||||
for _, value := range values {
|
||||
if err := requiredWorkerFields(value, t.Elem()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if t.Kind() != reflect.Struct {
|
||||
return nil
|
||||
}
|
||||
var fields map[string]json.RawMessage
|
||||
if json.Unmarshal(raw, &fields) != nil {
|
||||
return fault.Invalid("invalid worker object")
|
||||
}
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
field := t.Field(i)
|
||||
tag := strings.Split(field.Tag.Get("json"), ",")
|
||||
if tag[0] == "" || tag[0] == "-" {
|
||||
continue
|
||||
}
|
||||
value, found := fields[tag[0]]
|
||||
optional, numberString := false, false
|
||||
for _, flag := range tag[1:] {
|
||||
optional = optional || flag == "omitempty"
|
||||
numberString = numberString || flag == "string"
|
||||
}
|
||||
if !found {
|
||||
if optional {
|
||||
continue
|
||||
}
|
||||
return fault.Invalid("missing required worker field")
|
||||
}
|
||||
if field.Type.Kind() == reflect.Uint64 && numberString {
|
||||
var text string
|
||||
if json.Unmarshal(value, &text) != nil {
|
||||
return fault.Invalid("worker counter must be a decimal string")
|
||||
}
|
||||
n, err := strconv.ParseUint(text, 10, 64)
|
||||
if err != nil || n == 0 || strconv.FormatUint(n, 10) != text {
|
||||
return fault.Invalid("invalid worker counter")
|
||||
}
|
||||
}
|
||||
if err := requiredWorkerFields(value, field.Type); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user