Default GET /metrics is Prometheus text; JSON stays at /metrics.json and ?format=json. :8443 needs the metrics caller bearer; loopback stays open. Restricted callers have no per-caller series and no tavily:restricted gauge.
224 lines
5.5 KiB
Go
224 lines
5.5 KiB
Go
// Package fleetctx is Honcho retrieval for sift. Read-only.
|
|
// The restricted workspace (guardian-star) is reachable only when the
|
|
// authenticated caller is in Client.Permit and the caller's mapped workspace
|
|
// is that name. Workspace is never taken from the request.
|
|
package fleetctx
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"log/slog"
|
|
"net/http"
|
|
"net/url"
|
|
"regexp"
|
|
"strings"
|
|
"sync/atomic"
|
|
"time"
|
|
"unicode/utf8"
|
|
|
|
"git.wntrmute.dev/kyle/sift/internal/auth"
|
|
)
|
|
|
|
const (
|
|
Restricted = "guardian-star"
|
|
Timeout = 3 * time.Second
|
|
Limit = 5
|
|
MaxChars = 300
|
|
)
|
|
|
|
type Item struct {
|
|
Peer string `json:"peer"`
|
|
Content string `json:"content"`
|
|
CreatedAt string `json:"created_at"`
|
|
Session string `json:"session"`
|
|
}
|
|
|
|
type Client struct {
|
|
Base string
|
|
Allow []string
|
|
Permit []string // caller names allowed to query Restricted
|
|
HTTP *http.Client
|
|
Log *slog.Logger
|
|
Calls atomic.Uint64
|
|
Hits atomic.Uint64
|
|
Failures atomic.Uint64
|
|
}
|
|
|
|
func restricted(ws string) bool {
|
|
return strings.EqualFold(strings.TrimSpace(ws), Restricted)
|
|
}
|
|
|
|
func permitted(caller string, permit []string) bool {
|
|
if caller == "" {
|
|
return false
|
|
}
|
|
for _, p := range permit {
|
|
if p == caller {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
var workspaceName = regexp.MustCompile(`^[A-Za-z0-9_-]+$`)
|
|
|
|
func workspaceOK(ws string) bool {
|
|
return workspaceName.MatchString(ws)
|
|
}
|
|
|
|
func allowed(ws string, allow []string) bool {
|
|
for _, a := range allow {
|
|
if a == ws {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Search returns prior knowledge for workspace. Failure → (nil, warning). Never
|
|
// an error that fails the fetch.
|
|
// Restricted workspace requires caller ∈ Permit. A permitted caller whose mapped
|
|
// workspace is not Restricted is refused and never queried (no fallback).
|
|
func (c *Client) Search(ctx context.Context, workspace, caller, query string) (items []Item, warn string) {
|
|
if c == nil || query == "" {
|
|
return nil, ""
|
|
}
|
|
if permitted(caller, c.Permit) && !restricted(workspace) {
|
|
if c.Log != nil {
|
|
c.Log.Error("restricted caller mapped off guardian-star", "caller", caller, "workspace", workspace)
|
|
}
|
|
c.Failures.Add(1)
|
|
return nil, "context refused: workspace"
|
|
}
|
|
if restricted(workspace) && !permitted(caller, c.Permit) {
|
|
if c.Log != nil {
|
|
c.Log.Warn("context refused", "workspace", workspace, "caller", caller)
|
|
}
|
|
c.Failures.Add(1)
|
|
return nil, "context refused: workspace"
|
|
}
|
|
if !workspaceOK(workspace) {
|
|
c.Failures.Add(1)
|
|
return nil, "context skipped: workspace"
|
|
}
|
|
if !allowed(workspace, c.Allow) {
|
|
if c.Log != nil {
|
|
c.Log.Warn("context workspace not allowlisted", "workspace", workspace)
|
|
}
|
|
return nil, "context skipped: workspace"
|
|
}
|
|
c.Calls.Add(1)
|
|
sctx, cancel := context.WithTimeout(ctx, Timeout)
|
|
defer cancel()
|
|
path := strings.TrimRight(c.Base, "/") + "/v3/workspaces/" + url.PathEscape(workspace) + "/search"
|
|
payload, _ := json.Marshal(map[string]any{"query": query, "limit": Limit})
|
|
req, err := http.NewRequestWithContext(sctx, http.MethodPost, path, bytes.NewReader(payload))
|
|
if err != nil {
|
|
c.Failures.Add(1)
|
|
return nil, "context failed"
|
|
}
|
|
req.Header.Set("Content-Type", "application/json")
|
|
httpClient := c.HTTP
|
|
if httpClient == nil {
|
|
httpClient = &http.Client{Timeout: Timeout}
|
|
}
|
|
resp, err := httpClient.Do(req)
|
|
if err != nil {
|
|
c.Failures.Add(1)
|
|
return nil, "context failed"
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != 200 {
|
|
c.Failures.Add(1)
|
|
return nil, "context failed"
|
|
}
|
|
var raw []struct {
|
|
Content string `json:"content"`
|
|
CreatedAt string `json:"created_at"`
|
|
PeerID string `json:"peer_id"`
|
|
SessionID string `json:"session_id"`
|
|
WorkspaceID string `json:"workspace_id"`
|
|
}
|
|
if err := json.NewDecoder(resp.Body).Decode(&raw); err != nil {
|
|
c.Failures.Add(1)
|
|
return nil, "context failed"
|
|
}
|
|
for _, m := range raw {
|
|
if m.WorkspaceID != workspace {
|
|
continue
|
|
}
|
|
content := m.Content
|
|
if utf8.RuneCountInString(content) > MaxChars {
|
|
r := []rune(content)
|
|
content = string(r[:MaxChars])
|
|
}
|
|
items = append(items, Item{
|
|
Peer: m.PeerID,
|
|
Content: content,
|
|
CreatedAt: m.CreatedAt,
|
|
Session: m.SessionID,
|
|
})
|
|
}
|
|
if len(items) > 0 {
|
|
c.Hits.Add(1)
|
|
}
|
|
return items, ""
|
|
}
|
|
|
|
func Query(question, title string, headings []string) string {
|
|
if q := strings.TrimSpace(question); q != "" {
|
|
return q
|
|
}
|
|
var b strings.Builder
|
|
b.WriteString(title)
|
|
n := 0
|
|
for _, h := range headings {
|
|
if n >= 3 {
|
|
break
|
|
}
|
|
if strings.TrimSpace(h) == "" {
|
|
continue
|
|
}
|
|
b.WriteByte(' ')
|
|
b.WriteString(h)
|
|
n++
|
|
}
|
|
return strings.TrimSpace(b.String())
|
|
}
|
|
|
|
func (c *Client) String() string { return fmt.Sprintf("fleetctx base=%s", c.Base) }
|
|
|
|
// CallerWS is a keys.toml name+workspace pair for startup/reload checks.
|
|
type CallerWS struct {
|
|
Name, Workspace string
|
|
}
|
|
|
|
// CheckMappings logs ERROR for every permitted caller whose workspace is not
|
|
// Restricted, and WARN for every permit_restricted name with no key at all.
|
|
func (c *Client) CheckMappings(callers []CallerWS) {
|
|
if c == nil {
|
|
return
|
|
}
|
|
have := make(map[string]struct{}, len(callers))
|
|
for _, a := range callers {
|
|
have[a.Name] = struct{}{}
|
|
if a.Name == auth.NameMetrics {
|
|
continue
|
|
}
|
|
if permitted(a.Name, c.Permit) && !restricted(a.Workspace) {
|
|
if c.Log != nil {
|
|
c.Log.Error("restricted caller mapped off guardian-star", "caller", a.Name, "workspace", a.Workspace)
|
|
}
|
|
}
|
|
}
|
|
for _, name := range c.Permit {
|
|
if _, ok := have[name]; !ok {
|
|
if c.Log != nil {
|
|
c.Log.Warn("permit_restricted caller has no key", "caller", name)
|
|
}
|
|
}
|
|
}
|
|
}
|