Sign Run requests with observed platform clock

This commit is contained in:
npc0-hue
2026-08-31 18:23:04 +08:00
parent 40b704d60f
commit cde99e19fa
2 changed files with 103 additions and 7 deletions
+62 -7
View File
@@ -15,16 +15,20 @@ import (
"log"
"net/http"
"net/url"
"reflect"
"strconv"
"strings"
"sync/atomic"
"time"
"browser.local/run/protocol"
)
type PlatformClient struct {
baseURL string
httpClient *http.Client
baseURL string
httpClient *http.Client
serverClockOffsetNanos *atomic.Int64
serverClockOffsetActive *atomic.Bool
}
type PlatformRequestError struct {
@@ -134,7 +138,7 @@ func NewPlatformClientWithHTTPClient(rawURL string, httpClient *http.Client) (Pl
httpClient = http.DefaultClient
}
return PlatformClient{baseURL: strings.TrimRight(parsed.String(), "/"), httpClient: httpClient}, nil
return PlatformClient{baseURL: strings.TrimRight(parsed.String(), "/"), httpClient: httpClient, serverClockOffsetNanos: &atomic.Int64{}, serverClockOffsetActive: &atomic.Bool{}}, nil
}
func (c PlatformClient) BaseURL() string {
@@ -166,7 +170,7 @@ func (c PlatformClient) StreamControlEvents(ctx context.Context, request protoco
}
httpRequest.Header.Set("Content-Type", "application/json")
httpRequest.Header.Set("Accept", "text/event-stream")
signatureSummary, err := signRunRequest(httpRequest, body.Bytes())
signatureSummary, err := signRunRequest(httpRequest, body.Bytes(), c.signatureTime())
if err != nil {
return err
}
@@ -348,7 +352,7 @@ func postPlatformJSON[Request any, Response any](ctx context.Context, client Pla
httpRequest.Header.Set("Content-Type", "application/json")
httpRequest.Header.Set("Accept", "application/json")
if path != "/api/v1/run/control/hello" {
signatureSummary, err := signRunRequest(httpRequest, body.Bytes())
signatureSummary, err := signRunRequest(httpRequest, body.Bytes(), client.signatureTime())
if err != nil {
log.Printf("RUN platform request status=sign_error method=POST base=%s path=%s durationMs=%d error=%s", diagnosticLogValue(client.baseURL), path, time.Since(startedAt).Milliseconds(), err)
return response, err
@@ -375,6 +379,7 @@ func postPlatformJSON[Request any, Response any](ctx context.Context, client Pla
if err := json.NewDecoder(httpResponse.Body).Decode(&response); err != nil {
return response, fmt.Errorf("decode platform response: %w", err)
}
client.observeResponseServerTime(response)
return response, nil
}
@@ -391,7 +396,7 @@ type runRequestSignatureSummary struct {
Signature string
}
func signRunRequest(request *http.Request, body []byte) (runRequestSignatureSummary, error) {
func signRunRequest(request *http.Request, body []byte, stamp time.Time) (runRequestSignatureSummary, error) {
var envelope runRequestEnvelope
if err := json.Unmarshal(body, &envelope); err != nil {
return runRequestSignatureSummary{}, fmt.Errorf("decode Run signing envelope: %w", err)
@@ -403,7 +408,7 @@ func signRunRequest(request *http.Request, body []byte) (runRequestSignatureSumm
if _, err := rand.Read(nonceBytes); err != nil {
return runRequestSignatureSummary{}, fmt.Errorf("create Run request nonce: %w", err)
}
timestamp := strconv.FormatInt(time.Now().UTC().Unix(), 10)
timestamp := strconv.FormatInt(stamp.UTC().Unix(), 10)
nonce := hex.EncodeToString(nonceBytes)
bodyHash := sha256.Sum256(body)
bodyHashHex := hex.EncodeToString(bodyHash[:])
@@ -418,6 +423,56 @@ func signRunRequest(request *http.Request, body []byte) (runRequestSignatureSumm
return runRequestSignatureSummary{RunEndpointID: envelope.RunEndpointID, Timestamp: timestamp, Nonce: nonce, BodyHash: bodyHashHex, Signature: signature}, nil
}
func (c PlatformClient) signatureTime() time.Time {
now := time.Now().UTC()
if c.serverClockOffsetActive != nil && c.serverClockOffsetActive.Load() && c.serverClockOffsetNanos != nil {
return now.Add(time.Duration(c.serverClockOffsetNanos.Load()))
}
return now
}
func (c PlatformClient) observeResponseServerTime(response any) {
serverTime, ok := responseServerTime(response)
if !ok || serverTime.IsZero() || c.serverClockOffsetNanos == nil || c.serverClockOffsetActive == nil {
return
}
observedAt := time.Now().UTC()
offset := serverTime.UTC().Sub(observedAt)
previousActive := c.serverClockOffsetActive.Load()
previous := time.Duration(c.serverClockOffsetNanos.Load())
c.serverClockOffsetNanos.Store(int64(offset))
c.serverClockOffsetActive.Store(true)
if !previousActive || absDuration(offset-previous) > time.Second {
log.Printf("RUN platform clock status=calibrated offsetMs=%d serverTime=%s", offset.Milliseconds(), serverTime.UTC().Format(time.RFC3339))
}
}
func responseServerTime(response any) (time.Time, bool) {
value := reflect.ValueOf(response)
for value.Kind() == reflect.Pointer || value.Kind() == reflect.Interface {
if value.IsNil() {
return time.Time{}, false
}
value = value.Elem()
}
if value.Kind() != reflect.Struct {
return time.Time{}, false
}
field := value.FieldByName("ServerTime")
if !field.IsValid() || !field.CanInterface() {
return time.Time{}, false
}
serverTime, ok := field.Interface().(time.Time)
return serverTime, ok
}
func absDuration(value time.Duration) time.Duration {
if value < 0 {
return -value
}
return value
}
func shortDiagnosticValue(value string) string {
value = diagnosticLogValue(value)
if value == "" {