fix: 6 bugs (timeline path value, seer SSE cursor, erasure checksum, subtle compare, build status code, dead WorkerName branch)
Some checks failed
Test / test (push) Has been cancelled
Some checks failed
Test / test (push) Has been cancelled
This commit is contained in:
@@ -70,7 +70,7 @@ func (h *ForgeHandler) TriggerBuild(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
builds, err := h.Pipeline.BuildAll(req.Public)
|
builds, err := h.Pipeline.BuildAll(req.Public)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
auth.JSON(w, http.StatusOK, map[string]any{
|
auth.JSON(w, http.StatusInternalServerError, map[string]any{
|
||||||
"ok": false,
|
"ok": false,
|
||||||
"message": err.Error(),
|
"message": err.Error(),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -36,10 +36,15 @@ func (h *SeerHandler) Stream(w http.ResponseWriter, r *http.Request) {
|
|||||||
w.Header().Set("Cache-Control", "no-cache")
|
w.Header().Set("Cache-Control", "no-cache")
|
||||||
w.Header().Set("Connection", "keep-alive")
|
w.Header().Set("Connection", "keep-alive")
|
||||||
|
|
||||||
|
// Send last 20 events as backlog, track the most recent timestamp as cursor.
|
||||||
|
var cursor string
|
||||||
events, _ := h.Store.ListSeerEvents(20)
|
events, _ := h.Store.ListSeerEvents(20)
|
||||||
for _, ev := range events {
|
for _, ev := range events {
|
||||||
payload, _ := json.Marshal(ev)
|
payload, _ := json.Marshal(ev)
|
||||||
fmt.Fprintf(w, "data: %s\n\n", payload)
|
fmt.Fprintf(w, "data: %s\n\n", payload)
|
||||||
|
if ev.CreatedAt > cursor {
|
||||||
|
cursor = ev.CreatedAt
|
||||||
|
}
|
||||||
}
|
}
|
||||||
flusher.Flush()
|
flusher.Flush()
|
||||||
|
|
||||||
@@ -51,13 +56,18 @@ func (h *SeerHandler) Stream(w http.ResponseWriter, r *http.Request) {
|
|||||||
case <-r.Context().Done():
|
case <-r.Context().Done():
|
||||||
return
|
return
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
events, err := h.Store.ListSeerEvents(5)
|
newEvents, err := h.Store.ListSeerEventsSince(cursor, 50)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for _, ev := range events {
|
for _, ev := range newEvents {
|
||||||
payload, _ := json.Marshal(ev)
|
payload, _ := json.Marshal(ev)
|
||||||
fmt.Fprintf(w, "data: %s\n\n", payload)
|
fmt.Fprintf(w, "data: %s\n\n", payload)
|
||||||
|
if ev.CreatedAt > cursor {
|
||||||
|
cursor = ev.CreatedAt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(newEvents) > 0 {
|
||||||
flusher.Flush()
|
flusher.Flush()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ func (h *SeerHub) fetchEvents(ctx context.Context, since string) ([]SeerEvent, e
|
|||||||
// TimelineHandler serves LOTL timeline merged with court events for a host.
|
// TimelineHandler serves LOTL timeline merged with court events for a host.
|
||||||
func TimelineHandler(c *Court) http.HandlerFunc {
|
func TimelineHandler(c *Court) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
hostID := r.PathValue("host_id")
|
hostID := r.PathValue("id")
|
||||||
if hostID == "" {
|
if hostID == "" {
|
||||||
http.Error(w, "host_id required", http.StatusBadRequest)
|
http.Error(w, "host_id required", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -176,12 +176,12 @@ func (s *Service) Reconstruct(ctx context.Context, bundleID string, indices []in
|
|||||||
for i := 0; i < bundle.DataShards; i++ {
|
for i := 0; i < bundle.DataShards; i++ {
|
||||||
out = append(out, shards[i]...)
|
out = append(out, shards[i]...)
|
||||||
}
|
}
|
||||||
// Trim padding — find actual length via checksum match
|
trimmed := trimNullPadding(out)
|
||||||
sum := sha256.Sum256(out)
|
sum := sha256.Sum256(trimmed)
|
||||||
if hex.EncodeToString(sum[:]) != bundle.Checksum {
|
if hex.EncodeToString(sum[:]) != bundle.Checksum {
|
||||||
// Return best-effort; caller validates
|
return nil, fmt.Errorf("reconstructed data checksum mismatch: bundle may be corrupt")
|
||||||
}
|
}
|
||||||
return trimNullPadding(out), nil
|
return trimmed, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func trimNullPadding(b []byte) []byte {
|
func trimNullPadding(b []byte) []byte {
|
||||||
|
|||||||
@@ -51,6 +51,47 @@ type SeerEvent struct {
|
|||||||
CreatedAt string `json:"created_at"`
|
CreatedAt string `json:"created_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ListSeerEventsSince returns events newer than the given SQLite timestamp string (exclusive).
|
||||||
|
func (s *Store) ListSeerEventsSince(since string, limit int) ([]SeerEvent, error) {
|
||||||
|
if limit <= 0 {
|
||||||
|
limit = 50
|
||||||
|
}
|
||||||
|
var rows interface {
|
||||||
|
Close() error
|
||||||
|
Next() bool
|
||||||
|
Scan(dest ...any) error
|
||||||
|
Err() error
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
if since == "" {
|
||||||
|
rows, err = s.db.Query(`
|
||||||
|
SELECT id, host_id, event_type, payload_json, created_at
|
||||||
|
FROM seer_events ORDER BY created_at ASC LIMIT ?`, limit)
|
||||||
|
} else {
|
||||||
|
rows, err = s.db.Query(`
|
||||||
|
SELECT id, host_id, event_type, payload_json, created_at
|
||||||
|
FROM seer_events WHERE created_at > ? ORDER BY created_at ASC LIMIT ?`, since, limit)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
var events []SeerEvent
|
||||||
|
for rows.Next() {
|
||||||
|
var e SeerEvent
|
||||||
|
var hostID sql.NullString
|
||||||
|
if err := rows.Scan(&e.ID, &hostID, &e.EventType, &e.PayloadJSON, &e.CreatedAt); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if hostID.Valid {
|
||||||
|
e.HostID = hostID.String
|
||||||
|
}
|
||||||
|
events = append(events, e)
|
||||||
|
}
|
||||||
|
return events, rows.Err()
|
||||||
|
}
|
||||||
|
|
||||||
func nullString(s string) sql.NullString {
|
func nullString(s string) sql.NullString {
|
||||||
if s == "" {
|
if s == "" {
|
||||||
return sql.NullString{}
|
return sql.NullString{}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package fleet
|
package fleet
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/subtle"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -329,14 +330,7 @@ func (h *Hub) HandleBeacon(store *Store) http.HandlerFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func constantTimeEqual(a, b string) bool {
|
func constantTimeEqual(a, b string) bool {
|
||||||
if len(a) != len(b) {
|
return subtle.ConstantTimeCompare([]byte(a), []byte(b)) == 1
|
||||||
return false
|
|
||||||
}
|
|
||||||
var v byte
|
|
||||||
for i := 0; i < len(a); i++ {
|
|
||||||
v |= a[i] ^ b[i]
|
|
||||||
}
|
|
||||||
return v == 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func coalesce(values ...string) string {
|
func coalesce(values ...string) string {
|
||||||
|
|||||||
Reference in New Issue
Block a user