Skip to content

Commit 65bc069

Browse files
aksOpsclaude
andcommitted
feat: ship multi-tenancy, Entra auth, retention, and pre-UI hardening
Backend - Multi-tenancy: tenant_id column + ctx propagation across all reads/writes; X-Tenant-ID HTTP header, x-tenant-id gRPC metadata, tenant.id resource attr; per-tenant API key file support that overrides client header. - Azure Entra ID DB auth (DefaultAzureCredential); strict sslmode gate (require|verify-ca|verify-full); 30m conn lifetime cap for token TTL. - Drain log clustering (pure-Go, persisted templates, O(1) LRU via container/list); retains TF-IDF vectordb as fallback similarity ranker. - RetentionScheduler: hourly batched purge + daily VACUUM/ANALYZE via raw sqlDB (escapes GORM implicit tx); atomic.Bool overlap guard; cross-tenant. - Self-signed TLS bootstrap: package mutex + flock cross-process serialization, ECDSA-P256, 10yr validity, SANs for localhost/hostname. - Panic recovery across HTTP middleware, gRPC interceptors, GraphRAG workers. - DB health 503 fast-fail middleware with 5s ping loop. - DLQ: typed envelopes, collision-safe tempfiles via os.CreateTemp. - Decompression bomb guard: 64 MiB io.LimitReader post-gzip. - OTel self-instrumentation via otlptracegrpc + otelhttp. - API auth failure metric hook (missing_header/bad_scheme/bad_key reasons). Storage fixes - PurgeTracesBatched: Unscoped().Delete() to avoid GORM soft-delete. - Span sweep: exclude spans whose trace still exists (clock-skew safe). - UpdateLogInsight: tenant-scoped WHERE + ErrLogNotFoundOrWrongTenant. - GetLogsV2 parallel queries now use WithContext(ctx). - Drain template ID collisions on merge folded into existing bucket. UI - Mantine v8 (scoped migration from Radix UI). - ErrorBoundary + WebSocket exponential backoff and heartbeat. - react-window virtualization for Traces/Logs pages. - Full TypeScript type contracts in ui/src/types/api.ts. Ops - docs/OPERATIONS.md (First Run, Production Checklist, Backup/Restore, Incident Response, Observability, Known Limitations, Upgrade Path). - .env.example covering every os.Getenv key. - CLAUDE.md synced with new subsystems. Verification: go build + go vet + golangci-lint (0 issues) + go test -race across all packages green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent adb6c76 commit 65bc069

125 files changed

Lines changed: 16685 additions & 7569 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.env.example

Lines changed: 128 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,131 @@
1-
# Application Environment
2-
APP_ENV=development
3-
HTTP_PORT=8080
4-
GRPC_PORT=4317
1+
# ==============================================================================
2+
# OtelContext — Environment Configuration Reference
3+
# ==============================================================================
4+
# Copy to `.env` and uncomment the lines you want to override.
5+
# Booleans accept (case-insensitive): 1 | true | yes | on (parseTruthy in config.go)
6+
# Lines shown as `=<default>` document the default applied when the var is unset.
7+
# Keys marked [REQUIRED IN PROD] must be set for any production deployment.
8+
# ------------------------------------------------------------------------------
59

6-
# Database Configuration
7-
# Options: mysql, sqlite, sqlserver
8-
DB_DRIVER=mysql
9-
DB_DSN=root:admin@tcp(127.0.0.1:3306)/OtelContext?charset=utf8mb4&parseTime=True&loc=Local
10+
# ---- Application ------------------------------------------------------------
11+
# APP_ENV=development # development|production — gates DevMode (WS origin checks relaxed)
12+
# LOG_LEVEL=INFO # DEBUG|INFO|WARN|ERROR
13+
# HTTP_PORT=8080 # HTTP API + OTLP HTTP + WebSocket + UI
14+
# GRPC_PORT=4317 # OTLP gRPC
1015

11-
# For SQLite:
12-
# DB_DRIVER=sqlite
13-
# DB_DSN=OtelContext.db
16+
# ---- Database ---------------------------------------------------------------
17+
# DB_DRIVER=sqlite # sqlite|postgres|mysql|sqlserver
18+
# DB_DSN= # driver-specific — pick ONE of the blocks below
1419

20+
# SQLite (default, zero-config):
21+
# DB_DSN=otelcontext.db
22+
23+
# PostgreSQL:
24+
# DB_DRIVER=postgres
25+
# DB_DSN=host=localhost user=otel password=otel dbname=otelcontext port=5432 sslmode=disable TimeZone=UTC
26+
27+
# MySQL:
28+
# DB_DRIVER=mysql
29+
# DB_DSN=root:admin@tcp(127.0.0.1:3306)/OtelContext?charset=utf8mb4&parseTime=True&loc=Local
30+
31+
# SQL Server:
32+
# DB_DRIVER=sqlserver
33+
# DB_DSN=sqlserver://user:password@host:1433?database=OtelContext
34+
35+
# DB_AUTOMIGRATE=true # GORM AutoMigrate on startup. Set false in Postgres prod (schema out-of-band)
36+
37+
# ---- Database Pool ----------------------------------------------------------
38+
# DB_MAX_OPEN_CONNS=50 # Max concurrent DB connections
39+
# DB_MAX_IDLE_CONNS=10 # Idle connections kept in pool
40+
# DB_CONN_MAX_LIFETIME=1h # Conn recycle window. Internally capped to 30m when DB_AZURE_AUTH=true
41+
42+
# ---- Azure Entra (passwordless Postgres) ------------------------------------
43+
# DB_AZURE_AUTH=false # Enables DefaultAzureCredential for Postgres. Requires strict TLS
44+
# # (sslmode=require|verify-ca|verify-full). DSN must omit password.
45+
# # Credential order: env vars → workload identity → managed identity → az CLI → dev creds.
46+
# # Local dev: `az login` is sufficient. AKS: workload or pod-managed identity.
47+
# DB_DSN=host=my-server.postgres.database.azure.com user=my-mi@tenant.onmicrosoft.com dbname=otelcontext port=5432 sslmode=require
48+
49+
# ---- TLS (HTTP + gRPC) ------------------------------------------------------
50+
# Explicit cert files take precedence over self-signed. Both files must be set together.
51+
# TLS_CERT_FILE=/etc/otelcontext/tls/server.crt
52+
# TLS_KEY_FILE=/etc/otelcontext/tls/server.key
53+
#
54+
# Self-signed bootstrap (dev/internal). Ignored if TLS_CERT_FILE is set.
55+
# Generates ECDSA-P256 cert at first start, caches under TLS_CACHE_DIR, reuses until expiry.
56+
# Clients must trust the generated cert (insecure skip or CA pin).
57+
# TLS_AUTO_SELFSIGNED=false
58+
# TLS_CACHE_DIR=./data/tls
59+
60+
# ---- Auth -------------------------------------------------------------------
61+
# API_KEY= # [REQUIRED IN PROD] Bearer token for /api/*, /v1/*, /mcp. Empty = auth disabled (dev only).
62+
63+
# ---- OTLP Ingest Filtering --------------------------------------------------
64+
# INGEST_MIN_SEVERITY=INFO # Drop logs below this severity before storage
65+
# INGEST_ALLOWED_SERVICES= # CSV allowlist of service.name (empty = accept all)
66+
# INGEST_EXCLUDED_SERVICES= # CSV denylist (applied after allowlist)
67+
68+
# ---- Adaptive Sampling ------------------------------------------------------
69+
# SAMPLING_RATE=1.0 # 0.0..1.0 probability for non-error, non-slow spans
70+
# SAMPLING_ALWAYS_ON_ERRORS=true # Keep every error span regardless of rate
71+
# SAMPLING_LATENCY_THRESHOLD_MS=500 # Keep every span slower than this
72+
73+
# ---- TSDB -------------------------------------------------------------------
74+
# TSDB_RING_BUFFER_DURATION=1h # In-memory metric ring buffer window (e.g. 30m, 2h)
75+
76+
# ---- GraphRAG / Cardinality / Vector ----------------------------------------
77+
# METRIC_ATTRIBUTE_KEYS= # CSV allowlist of attribute keys included in metric series key
78+
# METRIC_MAX_CARDINALITY=10000 # Max unique series per metric; new series dropped above this
79+
# VECTOR_INDEX_MAX_ENTRIES=100000 # TF-IDF index capacity (FIFO eviction)
80+
81+
# ---- DLQ (Dead Letter Queue) ------------------------------------------------
82+
# DLQ_PATH=./data/dlq # Directory for typed-envelope files
83+
# DLQ_REPLAY_INTERVAL=5m # Retry cadence with exponential backoff
84+
# DLQ_MAX_FILES=1000 # Cap on enqueued envelope count
85+
# DLQ_MAX_DISK_MB=500 # Disk budget — new writes fail when exceeded
86+
# DLQ_MAX_RETRIES=10 # Give up after this many failed replays
87+
88+
# ---- Rate Limiting ----------------------------------------------------------
89+
# API_RATE_LIMIT_RPS=100 # Per-IP token bucket rate for /api/*. 0 disables.
90+
91+
# ---- MCP Server -------------------------------------------------------------
92+
# MCP_ENABLED=true # Expose MCP JSON-RPC 2.0 (POST) + SSE (GET) for AI agents
93+
# MCP_PATH=/mcp # Mount path
94+
95+
# ---- Compression ------------------------------------------------------------
96+
# COMPRESSION_LEVEL=default # default|fast|best — zstd level for compressed columns
97+
98+
# ---- Retention --------------------------------------------------------------
99+
# HOT_RETENTION_DAYS=7 # RetentionScheduler purge cutoff. Range 1..36500. Set explicitly in prod.
100+
101+
# ---- OTel Self-Instrumentation ----------------------------------------------
102+
# OTEL_EXPORTER_OTLP_ENDPOINT= # When set, OtelContext exports its own spans to this OTLP gRPC endpoint.
103+
# # Use `localhost:4317` for dogfooding (self-ingest).
104+
105+
# ---- Multi-tenancy ----------------------------------------------------------
106+
# DEFAULT_TENANT=default # Tenant ID for rows ingested without X-Tenant-ID (HTTP) /
107+
# # x-tenant-id (gRPC metadata).
108+
#
109+
# OTLP_TRUST_RESOURCE_TENANT=false
110+
# # When true, OTLP ingest falls back to the `tenant.id` resource
111+
# # attribute if no header/metadata tenant was supplied. Disabled
112+
# # by default because resource attributes are client-controlled —
113+
# # a compromised SDK could forge another tenant's data. Only turn
114+
# # on in closed environments where every OTLP producer is trusted.
115+
#
116+
# API_TENANT_KEYS_FILE= # Path to a file of `key=tenant` pairs (one per line; `#` comments).
117+
# # When set, each API bearer token is bound to a specific tenant
118+
# # and the matched tenant OVERRIDES any X-Tenant-ID header —
119+
# # callers cannot read other tenants by swapping headers.
120+
# # When empty, behaviour falls back to the single shared API_KEY
121+
# # + self-asserted X-Tenant-ID header (legacy dev mode).
122+
123+
# ---- AI Service (optional — Azure OpenAI log insights) ----------------------
124+
# AI_ENABLED=false # Master switch. When false, AI workers are not started.
125+
# AZURE_OPENAI_ENDPOINT= # e.g. https://my-aoai.openai.azure.com/
126+
# AZURE_OPENAI_KEY= # API key
127+
# AZURE_OPENAI_MODEL= # Base model name
128+
# AZURE_OPENAI_DEPLOYMENT= # Deployment name (overrides MODEL if set)
129+
# AZURE_OPENAI_API_VERSION= # e.g. 2024-02-15-preview
130+
# AI_QUEUE_SIZE=100 # Backlog capacity for AI log analysis
131+
# AI_WORKER_POOL=3 # Concurrent AI workers

.golangci.yml

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# golangci-lint v2 configuration for OtelContext.
2+
# Curated set of linters — no "enable-all" sprawl.
3+
version: "2"
4+
5+
run:
6+
timeout: 5m
7+
8+
linters:
9+
default: none
10+
enable:
11+
# Core correctness
12+
- errcheck # unchecked errors
13+
- govet # standard vet
14+
- ineffassign # unused assignments
15+
- staticcheck # core correctness + Go 1.25 hints (rangeint, stringsseq, etc.)
16+
- unused # dead code
17+
18+
# Security & idioms
19+
- gosec # security issues
20+
- gocritic # idiomatic Go
21+
- misspell # comment/string typos
22+
- unconvert # redundant type conversions
23+
- prealloc # slice preallocation opportunities
24+
25+
# Resource hygiene
26+
- bodyclose # HTTP response body close
27+
- rowserrcheck # sql.Rows.Err() check
28+
- sqlclosecheck # sql resource leak detection
29+
30+
# Language pitfalls
31+
- copyloopvar # pre-Go-1.22 loop variable semantics
32+
- nolintlint # well-formed //nolint directives
33+
34+
exclusions:
35+
rules:
36+
# Test files commonly ignore errors intentionally (t.Cleanup, defer close, etc.).
37+
- path: _test\.go
38+
linters:
39+
- errcheck
40+
- gosec
41+
# Drain tests exercise edge cases that trip gosec heuristics.
42+
- path: internal/graphrag/drain_test\.go
43+
linters:
44+
- gosec
45+
- path: internal/graphrag/drain_aggressive_test\.go
46+
linters:
47+
- gosec
48+
# test/ contains microservice simulators (chaos testing). They intentionally
49+
# use math/rand for chaos probabilities (G404), ListenAndServe without timeouts
50+
# (G114), and log.Fatal (exitAfterDefer). Not production code.
51+
- path: ^test/
52+
linters:
53+
- gosec
54+
- gocritic
55+
56+
formatters:
57+
enable:
58+
- gofmt
59+
- goimports
60+
61+
issues:
62+
max-issues-per-linter: 0
63+
max-same-issues: 0
64+
65+
output:
66+
formats:
67+
text:
68+
path: stdout
69+
colors: true
70+
print-linter-name: true
71+
print-issued-lines: true

0 commit comments

Comments
 (0)