mirror of
https://github.com/hanzoai/o11y.git
synced 2026-10-15 12:58:33 +00:00
HIGH-1 amplification: cap events/envelope (1000); module.Ingest pre-aggregates occurrences by fingerprint and store.UpsertIssues writes the whole batch in ONE tx (a flood of identical events => one upsert, count+=N); per-org issue ceiling (10000) drops NEW fingerprints past the cap; per-org token-bucket rate limit (50/s burst 100) after DSN verify; retention/TTL sweep (DeleteStale) gated by O11Y_ERRORTRACKING_RETENTION_DAYS (default 90). MEDIUM-1: envelope item length is bounds-checked (>=0 && <= remaining) before it is added to pos — a MaxInt64/negative length no longer overflows the slice bound; falls back to newline framing. MEDIUM-2: default-on secret redaction (sk-/AKIA/bearer/JWT/conn-string/PAN, always) + PII scrub (email/IP) unless O11Y_ERRORTRACKING_CAPTURE_PII=true (fail-secure, mirrors llmobs capture-messages). Applied before the value enters the fingerprint. MEDIUM-3: DSN keys are versioned '<v>:<hmac>' with a per-org revocation watermark (o11y_ingest_revocations, wholesale-cached); rotate ONE org's min-version to revoke only its below-min DSNs — no global secret roll. LOW-1: lifecycle UpdateIssue is optimistic-concurrency guarded (version column, WHERE version=expected, bump on write; stale => 409 conflict, not clobber). Ingest never touches version. LOW-2: orgFromContext uses NewUUID+error (no MustNewUUID panic on a bad claim). INFO-1: corrected the occurrence-persistence claim + removed the dangling chsink.go reference (only NoopSink exists; logs sink is an honest fast-follow). Tests: 67 pass incl amplification-bounded (5000 events -> 1 upsert), event cap, per-org ceiling, MaxInt64/negative length no-panic, rate-limit, secret/PII scrub, versioned-key + isolated revocation, optimistic-update conflict, DeleteStale. Crown-jewel isolation/parity/DSN tests unchanged and still green.
83 lines
3.0 KiB
Go
83 lines
3.0 KiB
Go
package implerrortracking
|
|
|
|
import (
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/hanzoai/o11y/pkg/valuer"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
var testSecret = []byte("kms-platform-ingest-secret")
|
|
|
|
func TestVerifyKey_RoundTrip(t *testing.T) {
|
|
key := publicKeyFor(testSecret, "acme")
|
|
assert.True(t, verifyKey(testSecret, "acme", key, 0), "the derived key must verify for its project")
|
|
}
|
|
|
|
func TestVerifyKey_RejectsWrongProject(t *testing.T) {
|
|
key := publicKeyFor(testSecret, "acme")
|
|
assert.False(t, verifyKey(testSecret, "evil", key, 0), "a key minted for acme must not verify for another project")
|
|
}
|
|
|
|
func TestVerifyKey_RejectsWrongSecret(t *testing.T) {
|
|
key := publicKeyFor(testSecret, "acme")
|
|
assert.False(t, verifyKey([]byte("different-secret"), "acme", key, 0))
|
|
}
|
|
|
|
func TestVerifyKey_FailsClosed(t *testing.T) {
|
|
assert.False(t, verifyKey(nil, "acme", "anything", 0), "no secret => fail closed")
|
|
assert.False(t, verifyKey(testSecret, "acme", "", 0), "no presented key => fail closed")
|
|
}
|
|
|
|
// The MOST important parity test: the org UUID the ingest path derives from a DSN
|
|
// project MUST equal iamidentn.toUUID("org", slug) — otherwise a row written by
|
|
// ingest would be invisible to the org's IAM-authenticated reads. This replicates
|
|
// iamidentn's exact formula and asserts equality.
|
|
func TestOrgUUIDFromProject_MatchesIAMMapping(t *testing.T) {
|
|
for _, slug := range []string{"hanzo", "acme", "zoo"} {
|
|
want := uuid.NewSHA1(uuid.NameSpaceURL, []byte("hanzo:o11y:org:"+slug))
|
|
got, ok := orgUUIDFromProject(slug)
|
|
require.True(t, ok)
|
|
assert.Equal(t, want.String(), got.String(), "ingest org UUID must match the IAM read-path UUID for slug %q", slug)
|
|
}
|
|
}
|
|
|
|
func TestOrgUUIDFromProject_RawUUIDPassthrough(t *testing.T) {
|
|
u := valuer.GenerateUUID()
|
|
got, ok := orgUUIDFromProject(u.String())
|
|
require.True(t, ok)
|
|
assert.Equal(t, u.String(), got.String(), "a project that is already a UUID is used as-is")
|
|
}
|
|
|
|
func TestOrgUUIDFromProject_EmptyRejected(t *testing.T) {
|
|
_, ok := orgUUIDFromProject("")
|
|
assert.False(t, ok)
|
|
_, ok = orgUUIDFromProject(" ")
|
|
assert.False(t, ok)
|
|
}
|
|
|
|
func TestSentryKeyFromRequest_Header(t *testing.T) {
|
|
r := httptest.NewRequest("POST", "/api/acme/envelope/", nil)
|
|
r.Header.Set("X-Sentry-Auth", "Sentry sentry_version=7, sentry_key=pubkey123, sentry_client=sentry.python/1.40")
|
|
assert.Equal(t, "pubkey123", sentryKeyFromRequest(r))
|
|
}
|
|
|
|
func TestSentryKeyFromRequest_QueryFallback(t *testing.T) {
|
|
r := httptest.NewRequest("POST", "/api/acme/envelope/?sentry_key=qkey456", nil)
|
|
assert.Equal(t, "qkey456", sentryKeyFromRequest(r))
|
|
}
|
|
|
|
func TestSentryKeyFromRequest_HeaderWins(t *testing.T) {
|
|
r := httptest.NewRequest("POST", "/api/acme/envelope/?sentry_key=qkey", nil)
|
|
r.Header.Set("X-Sentry-Auth", "Sentry sentry_key=hkey")
|
|
assert.Equal(t, "hkey", sentryKeyFromRequest(r))
|
|
}
|
|
|
|
func TestMintDSN(t *testing.T) {
|
|
dsn := MintDSN(testSecret, "o11y.hanzo.ai", "acme")
|
|
assert.Equal(t, "https://"+publicKeyFor(testSecret, "acme")+"@o11y.hanzo.ai/v1/o11y/acme", dsn)
|
|
}
|