feat: expose signed platform health status

This commit is contained in:
Codex Local
2026-08-26 21:15:03 +08:00
parent 1e12d329dc
commit 47ef596d3b
30 changed files with 811 additions and 26 deletions
+20
View File
@@ -10,6 +10,26 @@ import (
"data-terminal-cloud/internal/idempotency"
)
func GetOpsPlatformHealthHandler(service *diagnostics.Service) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
principal, ok := webPrincipal(w, r)
if !ok {
return
}
tenantID, ok := opsTenantHeader(w, r)
if !ok {
return
}
item, err := service.GetLatestPlatformHealth(r.Context(), principal, tenantID)
if err != nil {
writeDiagnosticError(w, r, err)
return
}
w.Header().Set("Cache-Control", "no-store")
WriteJSON(w, r, http.StatusOK, item)
})
}
func ListOpsIntegrationIssuesHandler(service *diagnostics.Service) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
principal, ok := webPrincipal(w, r)
@@ -0,0 +1,93 @@
package httpapi
import (
"bytes"
"context"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"io"
"log/slog"
"net/http"
"strconv"
"strings"
"time"
"data-terminal-cloud/internal/diagnostics"
)
const (
HeaderPlatformTimestamp = "X-Platform-Timestamp"
HeaderPlatformSignature = "X-Platform-Signature"
)
type PlatformHealthProcessor interface {
IngestPlatformHealth(context.Context, diagnostics.PlatformHealthInput, []byte, time.Time) (diagnostics.PlatformHealthReceipt, error)
}
func PlatformHealthCallbackHandler(secret []byte, maxSkew time.Duration, processor PlatformHealthProcessor) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, 64*1024)
rawBody, err := io.ReadAll(r.Body)
if err != nil {
WriteError(w, r, http.StatusBadRequest, "INVALID_PLATFORM_HEALTH_REPORT", "服务器健康报告无法读取")
return
}
now := time.Now().UTC()
if !verifyPlatformHealthSignature(secret, r.Header.Get(HeaderPlatformTimestamp), r.Header.Get(HeaderPlatformSignature), rawBody, now, maxSkew) {
WriteError(w, r, http.StatusUnauthorized, "PLATFORM_HEALTH_AUTHENTICATION_FAILED", "服务器健康报告身份验证失败")
return
}
decoder := json.NewDecoder(bytes.NewReader(rawBody))
decoder.DisallowUnknownFields()
var input diagnostics.PlatformHealthInput
if err = decoder.Decode(&input); err != nil || ensureJSONEOF(decoder) != nil {
WriteError(w, r, http.StatusBadRequest, "INVALID_PLATFORM_HEALTH_REPORT", "服务器健康报告字段不正确")
return
}
receipt, err := processor.IngestPlatformHealth(r.Context(), input, rawBody, now)
if err != nil {
if errors.Is(err, diagnostics.ErrInvalid) {
WriteError(w, r, http.StatusBadRequest, "INVALID_PLATFORM_HEALTH_REPORT", "服务器健康报告字段不正确")
return
}
slog.ErrorContext(r.Context(), "platform health callback processing failed", "error", err)
WriteAPIError(w, r, http.StatusServiceUnavailable, Error{Code: "PLATFORM_HEALTH_UNAVAILABLE", Message: "服务器健康报告暂时无法处理", Retryable: true})
return
}
status := http.StatusCreated
if receipt.Duplicate {
status = http.StatusOK
}
w.Header().Set("Cache-Control", "no-store")
WriteJSON(w, r, status, receipt)
})
}
func verifyPlatformHealthSignature(secret []byte, timestamp, signature string, body []byte, now time.Time, maxSkew time.Duration) bool {
if len(secret) < 32 || maxSkew <= 0 || timestamp == "" || !strings.HasPrefix(signature, "sha256=") {
return false
}
epoch, err := strconv.ParseInt(timestamp, 10, 64)
if err != nil || absDuration(now.Sub(time.Unix(epoch, 0))) > maxSkew {
return false
}
provided, err := hex.DecodeString(strings.TrimPrefix(signature, "sha256="))
if err != nil {
return false
}
mac := hmac.New(sha256.New, secret)
_, _ = mac.Write([]byte(timestamp))
_, _ = mac.Write([]byte("."))
_, _ = mac.Write(body)
return hmac.Equal(provided, mac.Sum(nil))
}
func absDuration(value time.Duration) time.Duration {
if value < 0 {
return -value
}
return value
}
@@ -0,0 +1,78 @@
package httpapi
import (
"context"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"net/http"
"net/http/httptest"
"strconv"
"strings"
"testing"
"time"
"data-terminal-cloud/internal/diagnostics"
)
type fakePlatformHealthProcessor struct {
calls int
receipt diagnostics.PlatformHealthReceipt
err error
lastInput diagnostics.PlatformHealthInput
}
func (p *fakePlatformHealthProcessor) IngestPlatformHealth(_ context.Context, input diagnostics.PlatformHealthInput, _ []byte, _ time.Time) (diagnostics.PlatformHealthReceipt, error) {
p.calls++
p.lastInput = input
return p.receipt, p.err
}
func TestPlatformHealthCallbackRequiresSignatureAndAcceptsReport(t *testing.T) {
secret := []byte("01234567890123456789012345678901")
now := time.Now().UTC().Truncate(time.Second)
body, _ := json.Marshal(diagnostics.PlatformHealthInput{
Schema: diagnostics.PlatformHealthSchema, Status: "healthy", ObservedAt: now,
Checks: []diagnostics.PlatformHealthCheck{{Name: "public_https", Status: "healthy", Value: "http_200"}},
})
processor := &fakePlatformHealthProcessor{receipt: diagnostics.PlatformHealthReceipt{ID: "10000000-0000-0000-0000-000000000999", Status: "healthy"}}
handler := PlatformHealthCallbackHandler(secret, time.Minute, processor)
unsigned := httptest.NewRequest(http.MethodPost, "/callbacks/v1/platform-health", strings.NewReader(string(body)))
unsignedRecorder := httptest.NewRecorder()
handler.ServeHTTP(unsignedRecorder, unsigned)
if unsignedRecorder.Code != http.StatusUnauthorized || processor.calls != 0 {
t.Fatalf("unsigned status=%d calls=%d", unsignedRecorder.Code, processor.calls)
}
timestamp := strconv.FormatInt(now.Unix(), 10)
signed := httptest.NewRequest(http.MethodPost, "/callbacks/v1/platform-health", strings.NewReader(string(body)))
signed.Header.Set(HeaderPlatformTimestamp, timestamp)
signed.Header.Set(HeaderPlatformSignature, signPlatformHealth(secret, timestamp, body))
recorder := httptest.NewRecorder()
handler.ServeHTTP(recorder, signed)
if recorder.Code != http.StatusCreated || processor.calls != 1 || processor.lastInput.Schema != diagnostics.PlatformHealthSchema {
t.Fatalf("status=%d calls=%d input=%+v body=%s", recorder.Code, processor.calls, processor.lastInput, recorder.Body.String())
}
}
func TestPlatformHealthCallbackRejectsStaleAndMalformedSignature(t *testing.T) {
secret := []byte("01234567890123456789012345678901")
body := []byte(`{"schema":"platform-server-health-v1","status":"healthy","observed_at":"2026-08-26T00:00:00Z","checks":[]}`)
now := time.Now().UTC()
staleTimestamp := strconv.FormatInt(now.Add(-10*time.Minute).Unix(), 10)
if verifyPlatformHealthSignature(secret, staleTimestamp, signPlatformHealth(secret, staleTimestamp, body), body, now, time.Minute) {
t.Fatal("stale signature accepted")
}
if verifyPlatformHealthSignature(secret, strconv.FormatInt(now.Unix(), 10), "sha256=broken", body, now, time.Minute) {
t.Fatal("malformed signature accepted")
}
}
func signPlatformHealth(secret []byte, timestamp string, body []byte) string {
mac := hmac.New(sha256.New, secret)
_, _ = mac.Write([]byte(timestamp + "."))
_, _ = mac.Write(body)
return "sha256=" + hex.EncodeToString(mac.Sum(nil))
}
+9
View File
@@ -48,6 +48,9 @@ type RouterOptions struct {
ProviderCallbackSecret []byte
ProviderCallbackSkew time.Duration
ProviderCallbackProcessor ProviderCallbackProcessor
PlatformHealthCallbackSecret []byte
PlatformHealthCallbackSkew time.Duration
PlatformHealthProcessor PlatformHealthProcessor
}
func NewRouter(options RouterOptions) http.Handler {
@@ -117,6 +120,7 @@ func NewRouter(options RouterOptions) http.Handler {
mux.Handle("GET /api/v1/ops/cost-summary", authenticated(GetOpsCostSummaryHandler(options.BillingService)))
}
if options.DiagnosticService != nil {
mux.Handle("GET /api/v1/ops/diagnostics/platform-health", authenticated(GetOpsPlatformHealthHandler(options.DiagnosticService)))
mux.Handle("GET /api/v1/ops/integration-issues", authenticated(ListOpsIntegrationIssuesHandler(options.DiagnosticService)))
mux.Handle("POST /api/v1/ops/integration-issues", authenticated(CreateOpsIntegrationIssueHandler(options.DiagnosticService)))
mux.Handle("PATCH /api/v1/ops/integration-issues/{id}", authenticated(UpdateOpsIntegrationIssueHandler(options.DiagnosticService)))
@@ -278,6 +282,11 @@ func NewRouter(options RouterOptions) http.Handler {
options.ProviderCallbackSecret, options.ProviderCallbackSkew, options.ProviderCallbackProcessor,
))
}
if options.ServiceName == "callbacks" && len(options.PlatformHealthCallbackSecret) > 0 && options.PlatformHealthProcessor != nil {
mux.Handle("POST /callbacks/v1/platform-health", PlatformHealthCallbackHandler(
options.PlatformHealthCallbackSecret, options.PlatformHealthCallbackSkew, options.PlatformHealthProcessor,
))
}
mux.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
WriteError(writer, request, http.StatusNotFound, "RESOURCE_NOT_FOUND", "请求的资源不存在")
})