Files
cairnobs/api/internal/config/config.go
T
jcoffey-dev 3eb0f4c589 Phase 4: SSO scaffolding, RBAC enforcement, tenant-scoped dashboards, audit logging, K8s deployment
RBAC (api/internal/authz) is live on /query and /dashboards, backed by a
new enterprise/ module (session issuance, audit logging, RBAC storage,
OIDC/SAML protocol wiring) that core never imports -- only calls over
HTTP. Found and fixed a real cross-tenant vulnerability in dashboards
(no tenant_id filtering at all) while writing the threat model doc.

Two things are explicitly NOT done, documented rather than hidden:
tenant isolation for log data itself (/query still shares one ClickHouse
connection and Tantivy index across every tenant -- RBAC controls who
can query, not what a query can see), and human SSO login (protocol
wiring exists, no HTTP handler calls it yet). See
docs/security/threat-model.md and docs/phase-4-runbook.md.

Also adds deploy/ (Go Operator + Helm chart, validated offline only --
no cluster was reachable in this environment).
2026-08-13 22:16:59 -07:00

85 lines
2.5 KiB
Go

// Package config loads api's configuration from environment variables,
// same convention as /ingest: no config file format for Phase 0.
package config
import (
"fmt"
"os"
"strconv"
"time"
)
type Config struct {
HTTPListenAddr string
ClickHouse ClickHouseConfig
Postgres PostgresConfig
SearchGRPCAddr string
QueryTimeout time.Duration
CORSAllowedOrigin string
EnterpriseAuthURL string
}
type ClickHouseConfig struct {
Addr string
Database string
Username string
Password string
}
// PostgresConfig is the control-plane metadata store (dashboards, panels
// -- see /docs/phase-3-dashboard-design.md), distinct from ClickHouse
// which remains log-data-only.
type PostgresConfig struct {
Addr string
Database string
Username string
Password string
}
func Load() (Config, error) {
cfg := Config{
HTTPListenAddr: getenv("HTTP_LISTEN_ADDR", ":8080"),
ClickHouse: ClickHouseConfig{
Addr: getenv("CLICKHOUSE_ADDR", "localhost:9000"),
Database: getenv("CLICKHOUSE_DATABASE", "sentry"),
Username: getenv("CLICKHOUSE_USERNAME", "default"),
Password: getenv("CLICKHOUSE_PASSWORD", ""),
},
Postgres: PostgresConfig{
Addr: getenv("POSTGRES_ADDR", "localhost:5432"),
Database: getenv("POSTGRES_DATABASE", "sentry_metadata"),
Username: getenv("POSTGRES_USERNAME", "sentry"),
Password: getenv("POSTGRES_PASSWORD", ""),
},
// Search service's gRPC address (see /search) -- default matches
// /search's own default GRPC_LISTEN_ADDR.
SearchGRPCAddr: getenv("SEARCH_GRPC_ADDR", "localhost:50052"),
// Phase 0 has no auth, so this is wide open by default to keep
// the local SvelteKit dev server (a different origin/port)
// working out of the box. Tighten before this is ever reachable
// from outside a trusted dev/homelab network.
CORSAllowedOrigin: getenv("CORS_ALLOWED_ORIGIN", "*"),
// Empty by default -- a single-tenant deployment without
// enterprise/ configured runs with authz.RequireRole* as a
// no-op, matching Phase 0-3 behavior. Set to enterprise-auth's
// base URL (e.g. "http://enterprise-auth:8081") to turn on
// real session/service-token enforcement.
EnterpriseAuthURL: getenv("ENTERPRISE_AUTH_URL", ""),
}
timeoutSec, err := strconv.Atoi(getenv("QUERY_TIMEOUT_SECONDS", "30"))
if err != nil {
return Config{}, fmt.Errorf("QUERY_TIMEOUT_SECONDS: %w", err)
}
cfg.QueryTimeout = time.Duration(timeoutSec) * time.Second
return cfg, nil
}
func getenv(key, fallback string) string {
if v := os.Getenv(key); v != "" {
return v
}
return fallback
}