mirror of
https://github.com/J3vb/OwnCord.git
synced 2026-09-03 03:50:00 +03:00
chore: add dev tooling — Stryker, gremlins, k6, toxiproxy, Coraza WAF, Zod
Install and configure mutation testing (Stryker for client, go-gremlins for server), load testing (k6), chaos testing (toxiproxy), WAF middleware (Coraza with OWASP rules, opt-in via waf_enabled config), and Zod for runtime schema validation. All tools verified building cleanly.
This commit is contained in:
Generated
+1975
-3
File diff suppressed because it is too large
Load Diff
@@ -25,11 +25,16 @@
|
||||
"lint:ox": "oxlint src/",
|
||||
"format": "prettier --write \"src/**/*.ts\" \"tests/**/*.ts\"",
|
||||
"format:check": "prettier --check \"src/**/*.ts\" \"tests/**/*.ts\"",
|
||||
"knip": "knip"
|
||||
"knip": "knip",
|
||||
"test:mutate": "stryker run",
|
||||
"test:mutate:dry": "stryker run --dryRunOnly"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.39.4",
|
||||
"@playwright/test": "^1",
|
||||
"@stryker-mutator/core": "^9.6.0",
|
||||
"@stryker-mutator/typescript-checker": "^9.6.0",
|
||||
"@stryker-mutator/vitest-runner": "^9.6.0",
|
||||
"@tauri-apps/cli": "^2",
|
||||
"@vitest/browser": "^3.2.4",
|
||||
"@vitest/coverage-v8": "^3",
|
||||
@@ -64,6 +69,7 @@
|
||||
"@tauri-apps/plugin-process": "^2.3.1",
|
||||
"@tauri-apps/plugin-store": "^2",
|
||||
"@tauri-apps/plugin-updater": "^2.10.0",
|
||||
"livekit-client": "^2.18.0"
|
||||
"livekit-client": "^2.18.0",
|
||||
"zod": "^4.3.6"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
// @ts-check
|
||||
/** @type {import('@stryker-mutator/api/core').PartialStrykerOptions} */
|
||||
const config = {
|
||||
testRunner: "vitest",
|
||||
checkers: ["typescript"],
|
||||
tsconfigFile: "tsconfig.json",
|
||||
vitest: {
|
||||
configFile: "vitest.config.ts",
|
||||
},
|
||||
mutate: [
|
||||
"src/lib/**/*.ts",
|
||||
"src/stores/**/*.ts",
|
||||
"!src/lib/types.ts",
|
||||
"!src/**/*.d.ts",
|
||||
],
|
||||
reporters: ["html", "clear-text", "progress"],
|
||||
htmlReporter: {
|
||||
fileName: "reports/mutation/index.html",
|
||||
},
|
||||
thresholds: {
|
||||
high: 80,
|
||||
low: 60,
|
||||
break: 50,
|
||||
},
|
||||
concurrency: 4,
|
||||
timeoutMS: 30000,
|
||||
tempDirName: ".stryker-tmp",
|
||||
};
|
||||
|
||||
export default config;
|
||||
@@ -0,0 +1,37 @@
|
||||
# go-gremlins mutation testing configuration
|
||||
# Run: gremlins unleash --config .gremlins.yaml
|
||||
|
||||
unleash:
|
||||
# Packages to mutate
|
||||
packages:
|
||||
- ./ws/...
|
||||
- ./api/...
|
||||
- ./auth/...
|
||||
- ./db/...
|
||||
- ./config/...
|
||||
- ./storage/...
|
||||
- ./updater/...
|
||||
|
||||
# Run tests with race detector
|
||||
test_flags: "-race -count=1"
|
||||
|
||||
# Timeout per mutant test run
|
||||
timeout: 60s
|
||||
|
||||
# Number of parallel workers
|
||||
workers: 4
|
||||
|
||||
# Mutation operators to apply
|
||||
mutant_types:
|
||||
- CONDITIONALS_BOUNDARY # < to <=, > to >=
|
||||
- CONDITIONALS_NEGATION # == to !=
|
||||
- INCREMENT_DECREMENT # ++ to --
|
||||
- INVERT_NEGATIVES # -x to x
|
||||
- ARITHMETIC_BASE # + to -, * to /
|
||||
- INVERT_LOGICAL # && to ||
|
||||
- INVERT_LOOPCTRL # break to continue
|
||||
- REMOVE_SELF_ASSIGNMENTS # x += 1 to x
|
||||
|
||||
# Thresholds for pass/fail
|
||||
threshold:
|
||||
efficacy: 60
|
||||
@@ -36,6 +36,11 @@ func NewRouter(cfg *config.Config, database *db.DB, ver string, logBuf *admin.Ri
|
||||
r.Use(SecurityHeadersWithTLS(cfg.TLS.Mode))
|
||||
r.Use(MaxBodySizeUnless(defaultMaxBodySize, "/api/v1/uploads")) // upload route exempt
|
||||
|
||||
// Coraza WAF — opt-in via config.
|
||||
if cfg.Server.WAFEnabled {
|
||||
r.Use(NewWAFMiddleware(cfg.Server.WAFParanoiaLevel))
|
||||
}
|
||||
|
||||
// Health check — unauthenticated, no versioning prefix.
|
||||
// The online user count callback is set after hub creation below.
|
||||
var getOnlineUsers func() int
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
// Package api provides the HTTP router and handlers for the OwnCord server.
|
||||
//
|
||||
// waf.go implements Coraza WAF middleware for OWASP CRS protection.
|
||||
// Toggle via config: server.waf_enabled (default: false).
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
|
||||
"github.com/corazawaf/coraza/v3"
|
||||
"github.com/corazawaf/coraza/v3/types"
|
||||
)
|
||||
|
||||
// NewWAFMiddleware creates a Coraza WAF middleware with OWASP CRS rules.
|
||||
// paranoiaLevel controls rule sensitivity (1=low, 2=default, 3=strict, 4=paranoid).
|
||||
// Returns nil middleware if WAF creation fails (logged as error, server continues).
|
||||
func NewWAFMiddleware(paranoiaLevel int) func(http.Handler) http.Handler {
|
||||
if paranoiaLevel < 1 || paranoiaLevel > 4 {
|
||||
paranoiaLevel = 2
|
||||
}
|
||||
|
||||
waf, err := coraza.NewWAF(
|
||||
coraza.NewWAFConfig().
|
||||
WithDirectives(fmt.Sprintf(`
|
||||
SecRuleEngine On
|
||||
SecRequestBodyAccess On
|
||||
SecResponseBodyAccess Off
|
||||
SecRequestBodyLimit 1048576
|
||||
|
||||
# Paranoia level
|
||||
SecAction "id:900000,phase:1,pass,t:none,nolog,setvar:tx.blocking_paranoia_level=%d"
|
||||
|
||||
# Core rules — SQL injection
|
||||
SecRule ARGS|ARGS_NAMES|REQUEST_BODY "@detectSQLi" \
|
||||
"id:942100,phase:2,deny,status:403,log,msg:'SQL Injection detected',tag:'OWASP_CRS',tag:'attack-sqli'"
|
||||
|
||||
# Core rules — XSS
|
||||
SecRule ARGS|ARGS_NAMES|REQUEST_BODY "@detectXSS" \
|
||||
"id:941100,phase:2,deny,status:403,log,msg:'XSS detected',tag:'OWASP_CRS',tag:'attack-xss'"
|
||||
|
||||
# Path traversal
|
||||
SecRule ARGS|REQUEST_URI "@contains ../" \
|
||||
"id:930100,phase:2,deny,status:403,log,msg:'Path traversal detected',tag:'OWASP_CRS',tag:'attack-lfi'"
|
||||
|
||||
# Command injection patterns
|
||||
SecRule ARGS|REQUEST_BODY "@rx (?:;|\||\x60|&&|\$\()" \
|
||||
"id:932100,phase:2,deny,status:403,log,msg:'Command injection detected',tag:'OWASP_CRS',tag:'attack-rce'"
|
||||
|
||||
# Block common scanners
|
||||
SecRule REQUEST_HEADERS:User-Agent "@rx (?:nikto|sqlmap|nmap|masscan|dirbuster)" \
|
||||
"id:913100,phase:1,deny,status:403,log,msg:'Scanner blocked',tag:'OWASP_CRS',tag:'automation'"
|
||||
|
||||
# Exclude WebSocket upgrade and health endpoints from body inspection
|
||||
SecRule REQUEST_URI "@streq /ws" "id:900001,phase:1,pass,nolog,ctl:ruleRemoveById=942100;941100;932100"
|
||||
SecRule REQUEST_URI "@streq /api/v1/health" "id:900002,phase:1,pass,nolog,ctl:ruleRemoveById=942100;941100;932100"
|
||||
|
||||
# Exclude file upload endpoint from body inspection (binary content)
|
||||
SecRule REQUEST_URI "@beginsWith /api/v1/uploads" "id:900003,phase:1,pass,nolog,ctl:requestBodyAccess=Off"
|
||||
`, paranoiaLevel)),
|
||||
)
|
||||
if err != nil {
|
||||
slog.Error("waf: failed to create WAF engine, continuing without WAF", "error", err)
|
||||
return func(next http.Handler) http.Handler { return next }
|
||||
}
|
||||
|
||||
slog.Info("waf: Coraza WAF enabled", "paranoia_level", paranoiaLevel)
|
||||
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
tx := waf.NewTransaction()
|
||||
defer func() {
|
||||
tx.ProcessLogging()
|
||||
if err := tx.Close(); err != nil {
|
||||
slog.Debug("waf: error closing transaction", "error", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Process request headers
|
||||
tx.ProcessConnection(r.RemoteAddr, 0, "", 0)
|
||||
tx.ProcessURI(r.URL.String(), r.Method, r.Proto)
|
||||
for name, values := range r.Header {
|
||||
for _, value := range values {
|
||||
tx.AddRequestHeader(name, value)
|
||||
}
|
||||
}
|
||||
|
||||
if it := tx.ProcessRequestHeaders(); it != nil {
|
||||
handleWAFInterruption(w, it)
|
||||
return
|
||||
}
|
||||
|
||||
// Process request body (if applicable)
|
||||
if r.Body != nil && r.ContentLength > 0 {
|
||||
if it, _, err := tx.ReadRequestBodyFrom(r.Body); it != nil {
|
||||
handleWAFInterruption(w, it)
|
||||
return
|
||||
} else if err != nil {
|
||||
slog.Debug("waf: error reading request body", "error", err)
|
||||
}
|
||||
|
||||
if it, err := tx.ProcessRequestBody(); it != nil {
|
||||
handleWAFInterruption(w, it)
|
||||
return
|
||||
} else if err != nil {
|
||||
slog.Debug("waf: error processing request body", "error", err)
|
||||
}
|
||||
|
||||
// Replace body with buffered version so downstream handlers can read it
|
||||
reader, err := tx.RequestBodyReader()
|
||||
if err == nil && reader != nil {
|
||||
r.Body = io.NopCloser(reader)
|
||||
}
|
||||
}
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func handleWAFInterruption(w http.ResponseWriter, it *types.Interruption) {
|
||||
slog.Warn("waf: request blocked",
|
||||
"status", it.Status,
|
||||
"action", it.Action,
|
||||
"rule_id", it.RuleID,
|
||||
)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(it.Status)
|
||||
fmt.Fprintf(w, `{"error":"request blocked by security rules"}`)
|
||||
}
|
||||
@@ -50,6 +50,8 @@ type ServerConfig struct {
|
||||
AllowedOrigins []string `koanf:"allowed_origins"`
|
||||
TrustedProxies []string `koanf:"trusted_proxies"`
|
||||
AdminAllowedCIDRs []string `koanf:"admin_allowed_cidrs"`
|
||||
WAFEnabled bool `koanf:"waf_enabled"` // Enable Coraza WAF (default: false)
|
||||
WAFParanoiaLevel int `koanf:"waf_paranoia_level"` // OWASP CRS paranoia level 1-4 (default: 2)
|
||||
}
|
||||
|
||||
// DatabaseConfig holds database settings.
|
||||
|
||||
+28
-2
@@ -27,11 +27,15 @@ require (
|
||||
buf.build/go/protovalidate v1.1.2 // indirect
|
||||
buf.build/go/protoyaml v0.6.0 // indirect
|
||||
cel.dev/expr v0.25.1 // indirect
|
||||
github.com/Shopify/toxiproxy/v2 v2.12.0 // indirect
|
||||
github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
|
||||
github.com/aymerick/douceur v0.2.0 // indirect
|
||||
github.com/benbjohnson/clock v1.3.5 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bep/debounce v1.2.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/corazawaf/coraza/v3 v3.6.0 // indirect
|
||||
github.com/corazawaf/libinjection-go v0.3.2 // indirect
|
||||
github.com/dennwc/iters v1.2.2 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
@@ -43,10 +47,17 @@ require (
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||
github.com/goccy/go-json v0.10.5 // indirect
|
||||
github.com/goccy/go-yaml v1.18.0 // indirect
|
||||
github.com/google/cel-go v0.27.0 // indirect
|
||||
github.com/gorilla/css v1.0.1 // indirect
|
||||
github.com/gorilla/mux v1.8.1 // indirect
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
|
||||
github.com/gotnospirit/makeplural v0.0.0-20180622080156-a5f48d94d976 // indirect
|
||||
github.com/gotnospirit/messageformat v0.0.0-20221001023931-dfe49f1eb092 // indirect
|
||||
github.com/jxskiss/base62 v1.1.0 // indirect
|
||||
github.com/kaptinlin/go-i18n v0.1.4 // indirect
|
||||
github.com/kaptinlin/jsonschema v0.4.6 // indirect
|
||||
github.com/klauspost/compress v1.18.4 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/knadh/koanf/maps v0.1.2 // indirect
|
||||
@@ -55,15 +66,18 @@ require (
|
||||
github.com/livekit/mageutil v0.0.0-20250511045019-0f1ff63f7731 // indirect
|
||||
github.com/livekit/mediatransportutil v0.0.0-20251128105421-19c7a7b81c22 // indirect
|
||||
github.com/livekit/psrpc v0.7.1 // indirect
|
||||
github.com/magefile/mage v1.15.0 // indirect
|
||||
github.com/magefile/mage v1.15.1-0.20250615140142-78acbaf2e3ae // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/moby/sys/user v0.4.0 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/nats-io/nats.go v1.48.0 // indirect
|
||||
github.com/nats-io/nkeys v0.4.15 // indirect
|
||||
github.com/nats-io/nuid v1.0.1 // indirect
|
||||
github.com/ncruces/go-strftime v1.0.0 // indirect
|
||||
github.com/petar-dambovaliev/aho-corasick v0.0.0-20250424160509-463d218d4745 // indirect
|
||||
github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe // indirect
|
||||
github.com/pion/datachannel v1.6.0 // indirect
|
||||
github.com/pion/dtls/v3 v3.1.2 // indirect
|
||||
@@ -81,11 +95,21 @@ require (
|
||||
github.com/pion/transport/v4 v4.0.1 // indirect
|
||||
github.com/pion/turn/v4 v4.1.4 // indirect
|
||||
github.com/pion/webrtc/v4 v4.2.9 // indirect
|
||||
github.com/prometheus/client_golang v1.22.0 // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.64.0 // indirect
|
||||
github.com/prometheus/procfs v0.19.2 // indirect
|
||||
github.com/puzpuzpuz/xsync/v3 v3.5.1 // indirect
|
||||
github.com/redis/go-redis/v9 v9.17.2 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/rogpeppe/go-internal v1.10.0 // indirect
|
||||
github.com/rs/xid v1.5.0 // indirect
|
||||
github.com/rs/zerolog v1.33.0 // indirect
|
||||
github.com/tidwall/gjson v1.18.0 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/twitchtv/twirp v8.1.3+incompatible // indirect
|
||||
github.com/valllabh/ocsf-schema-golang v1.0.3 // indirect
|
||||
github.com/wlynxg/anet v0.0.5 // indirect
|
||||
github.com/zeebo/xxh3 v1.1.0 // indirect
|
||||
go.opentelemetry.io/otel v1.40.0 // indirect
|
||||
@@ -94,7 +118,7 @@ require (
|
||||
go.uber.org/zap v1.27.1 // indirect
|
||||
go.uber.org/zap/exp v0.3.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a // indirect
|
||||
golang.org/x/net v0.51.0 // indirect
|
||||
golang.org/x/net v0.52.0 // indirect
|
||||
golang.org/x/sync v0.20.0 // indirect
|
||||
golang.org/x/sys v0.42.0 // indirect
|
||||
golang.org/x/text v0.35.0 // indirect
|
||||
@@ -104,8 +128,10 @@ require (
|
||||
google.golang.org/grpc v1.79.3 // indirect
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
modernc.org/libc v1.70.0 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
modernc.org/memory v1.11.0 // indirect
|
||||
rsc.io/binaryregexp v0.2.0 // indirect
|
||||
)
|
||||
|
||||
@@ -14,12 +14,16 @@ github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERo
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw=
|
||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=
|
||||
github.com/Shopify/toxiproxy/v2 v2.12.0 h1:d1x++lYZg/zijXPPcv7PH0MvHMzEI5aX/YuUi/Sw+yg=
|
||||
github.com/Shopify/toxiproxy/v2 v2.12.0/go.mod h1:R9Z38Pw6k2cGZWXHe7tbxjGW9azmY1KbDQJ1kd+h7Tk=
|
||||
github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ=
|
||||
github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw=
|
||||
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
||||
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
||||
github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o=
|
||||
github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=
|
||||
github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
|
||||
github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4=
|
||||
@@ -38,6 +42,11 @@ github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG
|
||||
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
|
||||
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
|
||||
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
|
||||
github.com/corazawaf/coraza/v3 v3.6.0 h1:rfsGl6eRBzzUAyADFcpuO7qXLt0DZtYWhfTIuhcyAjQ=
|
||||
github.com/corazawaf/coraza/v3 v3.6.0/go.mod h1:q7gszZCSufoHIy9jV2NCgk+glYwZpP2mIKgbu2dZkvE=
|
||||
github.com/corazawaf/libinjection-go v0.3.2 h1:9rrKt0lpg4WvUXt+lwS06GywfqRXXsa/7JcOw5cQLwI=
|
||||
github.com/corazawaf/libinjection-go v0.3.2/go.mod h1:Ik/+w3UmTWH9yn366RgS9D95K3y7Atb5m/H/gXzzPCk=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
@@ -77,6 +86,11 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
|
||||
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/cel-go v0.27.0 h1:e7ih85+4qVrBuqQWTW4FKSqZYokVuc3HnhH5keboFTo=
|
||||
@@ -92,12 +106,22 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
|
||||
github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
|
||||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
|
||||
github.com/gotnospirit/makeplural v0.0.0-20180622080156-a5f48d94d976 h1:b70jEaX2iaJSPZULSUxKtm73LBfsCrMsIlYCUgNGSIs=
|
||||
github.com/gotnospirit/makeplural v0.0.0-20180622080156-a5f48d94d976/go.mod h1:ZGQeOwybjD8lkCjIyJfqR5LD2wMVHJ31d6GdPxoTsWY=
|
||||
github.com/gotnospirit/messageformat v0.0.0-20221001023931-dfe49f1eb092 h1:c7gcNWTSr1gtLp6PyYi3wzvFCEcHJ4YRobDgqmIgf7Q=
|
||||
github.com/gotnospirit/messageformat v0.0.0-20221001023931-dfe49f1eb092/go.mod h1:ZZAN4fkkful3l1lpJwF8JbW41ZiG9TwJ2ZlqzQovBNU=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||
github.com/jxskiss/base62 v1.1.0 h1:A5zbF8v8WXx2xixnAKD2w+abC+sIzYJX+nxmhA6HWFw=
|
||||
github.com/jxskiss/base62 v1.1.0/go.mod h1:HhWAlUXvxKThfOlZbcuFzsqwtF5TcqS9ru3y5GfjWAc=
|
||||
github.com/kaptinlin/go-i18n v0.1.4 h1:wCiwAn1LOcvymvWIVAM4m5dUAMiHunTdEubLDk4hTGs=
|
||||
github.com/kaptinlin/go-i18n v0.1.4/go.mod h1:g1fn1GvTgT4CiLE8/fFE1hboHWJ6erivrDpiDtCcFKg=
|
||||
github.com/kaptinlin/jsonschema v0.4.6 h1:vOSFg5tjmfkOdKg+D6Oo4fVOM/pActWu/ntkPsI1T64=
|
||||
github.com/kaptinlin/jsonschema v0.4.6/go.mod h1:1DUd7r5SdyB2ZnMtyB7uLv64dE3zTFTiYytDCd+AEL0=
|
||||
github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c=
|
||||
github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
@@ -135,6 +159,12 @@ github.com/livekit/server-sdk-go/v2 v2.16.0 h1:xbr6PLprgasruzEk4Qv2sHVcK6r+cebUv
|
||||
github.com/livekit/server-sdk-go/v2 v2.16.0/go.mod h1:+HCKTpzV21b/jvBtu+OmWbquUxaL74kHLI9ZwKmdhKU=
|
||||
github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg=
|
||||
github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
||||
github.com/magefile/mage v1.15.1-0.20250615140142-78acbaf2e3ae h1:yyMUG1VUd6IjV5jonMKpLXgwm9AzkfRsYisdCXc5OVI=
|
||||
github.com/magefile/mage v1.15.1-0.20250615140142-78acbaf2e3ae/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
|
||||
@@ -153,6 +183,8 @@ github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs=
|
||||
github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
|
||||
github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
|
||||
github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/nats-io/nats.go v1.48.0 h1:pSFyXApG+yWU/TgbKCjmm5K4wrHu86231/w84qRVR+U=
|
||||
github.com/nats-io/nats.go v1.48.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
|
||||
github.com/nats-io/nkeys v0.4.15 h1:JACV5jRVO9V856KOapQ7x+EY8Jo3qw1vJt/9Jpwzkk4=
|
||||
@@ -169,6 +201,8 @@ github.com/opencontainers/runc v1.3.3 h1:qlmBbbhu+yY0QM7jqfuat7M1H3/iXjju3VkP9lk
|
||||
github.com/opencontainers/runc v1.3.3/go.mod h1:D7rL72gfWxVs9cJ2/AayxB0Hlvn9g0gaF1R7uunumSI=
|
||||
github.com/ory/dockertest/v3 v3.12.0 h1:3oV9d0sDzlSQfHtIaB5k6ghUCVMVLpAY8hwrqoCyRCw=
|
||||
github.com/ory/dockertest/v3 v3.12.0/go.mod h1:aKNDTva3cp8dwOWwb9cWuX84aH5akkxXRvO7KCwWVjE=
|
||||
github.com/petar-dambovaliev/aho-corasick v0.0.0-20250424160509-463d218d4745 h1:Vpr4VgAizEgEZsaMohpw6JYDP+i9Of9dmdY4ufNP6HI=
|
||||
github.com/petar-dambovaliev/aho-corasick v0.0.0-20250424160509-463d218d4745/go.mod h1:EHPiTAKtiFmrMldLUNswFwfZ2eJIYBHktdaUTZxYWRw=
|
||||
github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe h1:vHpqOnPlnkba8iSxU4j/CvDSS9J4+F4473esQsYLGoE=
|
||||
github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
|
||||
github.com/pion/datachannel v1.6.0 h1:XecBlj+cvsxhAMZWFfFcPyUaDZtd7IJvrXqlXD/53i0=
|
||||
@@ -211,6 +245,14 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
|
||||
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
|
||||
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
||||
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||
github.com/prometheus/common v0.64.0 h1:pdZeA+g617P7oGv1CzdTzyeShxAGrTBsolKNOLQPGO4=
|
||||
github.com/prometheus/common v0.64.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
|
||||
github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws=
|
||||
github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.5.1 h1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++5Fg=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.5.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
||||
github.com/redis/go-redis/v9 v9.17.2 h1:P2EGsA4qVIM3Pp+aPocCJ7DguDHhqrXNhVcEp4ViluI=
|
||||
@@ -222,6 +264,10 @@ github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWg
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||
github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
|
||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
|
||||
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
||||
github.com/sasha-s/go-deadlock v0.3.9 h1:fiaT9rB7g5sr5ddNZvlwheclN9IP86eFW9WgqlEQV+w=
|
||||
github.com/sasha-s/go-deadlock v0.3.9/go.mod h1:KuZj51ZFmx42q/mPaYbRk0P1xcwe697zsJKE03vD4/Y=
|
||||
github.com/shoenig/test v1.7.0 h1:eWcHtTXa6QLnBvm0jgEabMRN/uJ4DMV3M8xUGgRkZmk=
|
||||
@@ -232,8 +278,17 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
|
||||
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/twitchtv/twirp v8.1.3+incompatible h1:+F4TdErPgSUbMZMwp13Q/KgDVuI7HJXP61mNV3/7iuU=
|
||||
github.com/twitchtv/twirp v8.1.3+incompatible/go.mod h1:RRJoFSAmTEh2weEqWtpPE3vFK5YBhA6bqp2l1kfCC5A=
|
||||
github.com/valllabh/ocsf-schema-golang v1.0.3 h1:eR8k/3jP/OOqB8LRCtdJ4U+vlgd/gk5y3KMXoodrsrw=
|
||||
github.com/valllabh/ocsf-schema-golang v1.0.3/go.mod h1:sZ3as9xqm1SSK5feFWIR2CuGeGRhsM7TR1MbpBctzPk=
|
||||
github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU=
|
||||
github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
|
||||
@@ -287,6 +342,8 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
|
||||
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
|
||||
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
|
||||
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -297,9 +354,11 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
||||
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
@@ -336,6 +395,8 @@ google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
@@ -369,3 +430,5 @@ modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
nhooyr.io/websocket v1.8.17 h1:KEVeLJkUywCKVsnLIDlD/5gtayKp8VoCkksHCGGfT9Y=
|
||||
nhooyr.io/websocket v1.8.17/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c=
|
||||
rsc.io/binaryregexp v0.2.0 h1:HfqmD5MEmC0zvwBuF187nq9mdnXjXsSivRiXN7SmRkE=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
// k6 WebSocket load test for OwnCord server
|
||||
// Run: k6 run --vus 50 --duration 60s scripts/k6/ws-load.js
|
||||
//
|
||||
// Environment variables:
|
||||
// K6_WS_URL - WebSocket URL (default: ws://localhost:8443/ws)
|
||||
// K6_HTTP_URL - HTTP base URL (default: http://localhost:8443)
|
||||
// K6_USERNAME - Test user prefix (default: loadtest)
|
||||
// K6_PASSWORD - Test user password (default: LoadTest123!)
|
||||
// K6_CHANNEL_ID - Channel ID to send messages in (default: 1)
|
||||
|
||||
import ws from "k6/ws";
|
||||
import http from "k6/http";
|
||||
import { check, sleep } from "k6";
|
||||
import { Counter, Rate, Trend } from "k6/metrics";
|
||||
|
||||
// Custom metrics
|
||||
const wsConnections = new Counter("ws_connections");
|
||||
const wsMessages = new Counter("ws_messages_sent");
|
||||
const wsErrors = new Counter("ws_errors");
|
||||
const wsConnectTime = new Trend("ws_connect_time", true);
|
||||
const wsMessageRate = new Rate("ws_message_success");
|
||||
const authTime = new Trend("auth_time", true);
|
||||
|
||||
// Configuration
|
||||
const WS_URL = __ENV.K6_WS_URL || "ws://localhost:8443/ws";
|
||||
const HTTP_URL = __ENV.K6_HTTP_URL || "http://localhost:8443";
|
||||
const USERNAME_PREFIX = __ENV.K6_USERNAME || "loadtest";
|
||||
const PASSWORD = __ENV.K6_PASSWORD || "LoadTest123!";
|
||||
const CHANNEL_ID = parseInt(__ENV.K6_CHANNEL_ID || "1");
|
||||
|
||||
export const options = {
|
||||
scenarios: {
|
||||
// Ramp up connections gradually
|
||||
websocket_load: {
|
||||
executor: "ramping-vus",
|
||||
startVUs: 0,
|
||||
stages: [
|
||||
{ duration: "10s", target: 10 }, // warm up
|
||||
{ duration: "30s", target: 50 }, // ramp to 50
|
||||
{ duration: "60s", target: 50 }, // sustain
|
||||
{ duration: "10s", target: 100 }, // spike
|
||||
{ duration: "30s", target: 100 }, // sustain spike
|
||||
{ duration: "10s", target: 0 }, // ramp down
|
||||
],
|
||||
},
|
||||
},
|
||||
thresholds: {
|
||||
ws_connect_time: ["p(95)<2000"], // 95% connect under 2s
|
||||
ws_message_success: ["rate>0.95"], // 95% message success
|
||||
ws_errors: ["count<50"], // fewer than 50 errors
|
||||
auth_time: ["p(95)<1000"], // 95% auth under 1s
|
||||
},
|
||||
};
|
||||
|
||||
// Login and get session token
|
||||
function authenticate(username) {
|
||||
const start = Date.now();
|
||||
const res = http.post(
|
||||
`${HTTP_URL}/api/v1/auth/login`,
|
||||
JSON.stringify({ username, password: PASSWORD }),
|
||||
{ headers: { "Content-Type": "application/json" } },
|
||||
);
|
||||
authTime.add(Date.now() - start);
|
||||
|
||||
if (res.status !== 200) {
|
||||
wsErrors.add(1);
|
||||
return null;
|
||||
}
|
||||
|
||||
const body = JSON.parse(res.body);
|
||||
return body.token;
|
||||
}
|
||||
|
||||
export default function () {
|
||||
const vuId = __VU;
|
||||
const username = `${USERNAME_PREFIX}${vuId}`;
|
||||
|
||||
// Authenticate
|
||||
const token = authenticate(username);
|
||||
if (!token) {
|
||||
sleep(1);
|
||||
return;
|
||||
}
|
||||
|
||||
// Connect WebSocket
|
||||
const connectStart = Date.now();
|
||||
const res = ws.connect(WS_URL, null, function (socket) {
|
||||
wsConnectTime.add(Date.now() - connectStart);
|
||||
wsConnections.add(1);
|
||||
|
||||
// Send auth on connect
|
||||
socket.send(
|
||||
JSON.stringify({
|
||||
type: "auth",
|
||||
token: token,
|
||||
}),
|
||||
);
|
||||
|
||||
// Handle incoming messages
|
||||
socket.on("message", function (msg) {
|
||||
try {
|
||||
const data = JSON.parse(msg);
|
||||
|
||||
// After auth_ok, focus a channel and start sending
|
||||
if (data.type === "ready") {
|
||||
socket.send(
|
||||
JSON.stringify({
|
||||
type: "channel_focus",
|
||||
channel_id: CHANNEL_ID,
|
||||
}),
|
||||
);
|
||||
}
|
||||
} catch (_e) {
|
||||
wsErrors.add(1);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("error", function (_e) {
|
||||
wsErrors.add(1);
|
||||
});
|
||||
|
||||
// Send messages periodically (respecting rate limits)
|
||||
let msgCount = 0;
|
||||
const maxMessages = 10;
|
||||
|
||||
socket.setInterval(function () {
|
||||
if (msgCount >= maxMessages) {
|
||||
socket.close();
|
||||
return;
|
||||
}
|
||||
|
||||
const msg = JSON.stringify({
|
||||
type: "chat_send",
|
||||
channel_id: CHANNEL_ID,
|
||||
content: `Load test message ${vuId}-${msgCount} at ${Date.now()}`,
|
||||
});
|
||||
|
||||
socket.send(msg);
|
||||
wsMessages.add(1);
|
||||
wsMessageRate.add(true);
|
||||
msgCount++;
|
||||
}, 2000); // 1 message every 2 seconds (well under rate limit)
|
||||
|
||||
// Send typing indicators
|
||||
socket.setInterval(function () {
|
||||
if (msgCount < maxMessages) {
|
||||
socket.send(
|
||||
JSON.stringify({
|
||||
type: "typing",
|
||||
channel_id: CHANNEL_ID,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}, 4000); // 1 typing every 4 seconds (under 1/3s limit)
|
||||
|
||||
// Send presence updates
|
||||
socket.setInterval(function () {
|
||||
socket.send(
|
||||
JSON.stringify({
|
||||
type: "presence",
|
||||
status: "online",
|
||||
}),
|
||||
);
|
||||
}, 15000); // 1 presence every 15 seconds (under 1/10s limit)
|
||||
|
||||
// Keep connection alive for the test duration
|
||||
socket.setTimeout(function () {
|
||||
socket.close();
|
||||
}, 25000);
|
||||
});
|
||||
|
||||
check(res, {
|
||||
"WebSocket status is 101": (r) => r && r.status === 101,
|
||||
});
|
||||
|
||||
if (!res || res.status !== 101) {
|
||||
wsErrors.add(1);
|
||||
wsMessageRate.add(false);
|
||||
}
|
||||
|
||||
sleep(1);
|
||||
}
|
||||
|
||||
export function handleSummary(data) {
|
||||
return {
|
||||
stdout: textSummary(data, { indent: " ", enableColors: true }),
|
||||
"reports/k6-summary.json": JSON.stringify(data, null, 2),
|
||||
};
|
||||
}
|
||||
|
||||
// Built-in k6 text summary
|
||||
function textSummary(data, opts) {
|
||||
// k6 handles this automatically when not overridden
|
||||
return JSON.stringify(data, null, 2);
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
#!/bin/bash
|
||||
# Toxiproxy chaos testing for OwnCord
|
||||
#
|
||||
# Prerequisites:
|
||||
# 1. toxiproxy-server running: toxiproxy-server &
|
||||
# 2. OwnCord server running on port 8443
|
||||
# 3. toxiproxy-cli available in PATH
|
||||
#
|
||||
# Usage: bash scripts/toxiproxy/chaos-test.sh
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
TOXI_HOST="${TOXI_HOST:-localhost:8474}"
|
||||
SERVER_HOST="${SERVER_HOST:-localhost}"
|
||||
SERVER_PORT="${SERVER_PORT:-8443}"
|
||||
PROXY_PORT="${PROXY_PORT:-18443}"
|
||||
|
||||
echo "=== OwnCord Chaos Testing ==="
|
||||
echo "Toxiproxy API: $TOXI_HOST"
|
||||
echo "Target server: $SERVER_HOST:$SERVER_PORT"
|
||||
echo "Proxy port: $PROXY_PORT"
|
||||
echo ""
|
||||
|
||||
# Create proxy
|
||||
echo "[1/7] Creating proxy..."
|
||||
toxiproxy-cli create owncord \
|
||||
--listen "0.0.0.0:$PROXY_PORT" \
|
||||
--upstream "$SERVER_HOST:$SERVER_PORT" 2>/dev/null || \
|
||||
echo " (proxy already exists)"
|
||||
|
||||
echo ""
|
||||
echo "[2/7] Test: Normal connectivity (baseline)"
|
||||
echo " Connect to localhost:$PROXY_PORT and verify response..."
|
||||
curl -sf "http://localhost:$PROXY_PORT/api/v1/health" && echo " OK" || echo " FAIL"
|
||||
sleep 1
|
||||
|
||||
echo ""
|
||||
echo "[3/7] Test: High latency (500ms)"
|
||||
echo " Simulates slow network / cross-region..."
|
||||
toxiproxy-cli toxic add owncord --type latency \
|
||||
--attribute latency=500 --attribute jitter=100 \
|
||||
--toxicName latency_test 2>/dev/null
|
||||
echo " Running health check with latency..."
|
||||
time curl -sf "http://localhost:$PROXY_PORT/api/v1/health" && echo " OK" || echo " FAIL"
|
||||
toxiproxy-cli toxic remove owncord --toxicName latency_test
|
||||
sleep 1
|
||||
|
||||
echo ""
|
||||
echo "[4/7] Test: Packet loss (30%)"
|
||||
echo " Simulates unreliable WiFi..."
|
||||
toxiproxy-cli toxic add owncord --type timeout \
|
||||
--attribute timeout=3000 \
|
||||
--toxicName timeout_test 2>/dev/null
|
||||
echo " Health check should timeout after 3s..."
|
||||
timeout 5 curl -sf "http://localhost:$PROXY_PORT/api/v1/health" 2>/dev/null && echo " OK (fast)" || echo " Timed out as expected"
|
||||
toxiproxy-cli toxic remove owncord --toxicName timeout_test
|
||||
sleep 1
|
||||
|
||||
echo ""
|
||||
echo "[5/7] Test: Bandwidth limit (10KB/s)"
|
||||
echo " Simulates throttled connection..."
|
||||
toxiproxy-cli toxic add owncord --type bandwidth \
|
||||
--attribute rate=10 \
|
||||
--toxicName bandwidth_test 2>/dev/null
|
||||
echo " Health check with bandwidth limit..."
|
||||
time curl -sf "http://localhost:$PROXY_PORT/api/v1/health" && echo " OK" || echo " FAIL"
|
||||
toxiproxy-cli toxic remove owncord --toxicName bandwidth_test
|
||||
sleep 1
|
||||
|
||||
echo ""
|
||||
echo "[6/7] Test: Connection reset"
|
||||
echo " Simulates abrupt disconnection..."
|
||||
toxiproxy-cli toxic add owncord --type reset_peer \
|
||||
--attribute timeout=1000 \
|
||||
--toxicName reset_test 2>/dev/null
|
||||
echo " Health check should fail after 1s..."
|
||||
curl -sf --max-time 3 "http://localhost:$PROXY_PORT/api/v1/health" 2>/dev/null && echo " OK (unexpected)" || echo " Reset as expected"
|
||||
toxiproxy-cli toxic remove owncord --toxicName reset_test
|
||||
sleep 1
|
||||
|
||||
echo ""
|
||||
echo "[7/7] Test: Downstream slicer (fragment responses)"
|
||||
echo " Simulates packet fragmentation..."
|
||||
toxiproxy-cli toxic add owncord --type slicer \
|
||||
--attribute average_size=10 --attribute size_variation=5 --attribute delay=10 \
|
||||
--toxicName slicer_test 2>/dev/null
|
||||
echo " Health check with sliced responses..."
|
||||
curl -sf "http://localhost:$PROXY_PORT/api/v1/health" && echo " OK" || echo " FAIL"
|
||||
toxiproxy-cli toxic remove owncord --toxicName slicer_test
|
||||
|
||||
echo ""
|
||||
echo "=== Cleanup ==="
|
||||
toxiproxy-cli delete owncord 2>/dev/null || true
|
||||
echo "Done. All chaos tests complete."
|
||||
Reference in New Issue
Block a user