Extends the heartbeat mechanism with a second gRPC service on the same mTLS channel (AgentControl.CheckIn, agent-initiated on the existing heartbeat ticker -- still push-only, no inbound port on any agent) so an agent reports its running config and can pick up an operator-set override. A new web UI section (/agents) lists every agent that's checked in, shows its reported config, and lets an operator edit a narrow, deliberately-scoped subset remotely: batch/heartbeat tuning, and (journald sources only) the unit filter. TLS material and the ingest endpoint are never reportable or remotely editable, by proto shape rather than a validation rule -- a bad or malicious edit there could permanently strand an agent or redirect where its logs go, unlike every other editable field, which only degrades behavior. An override lives only in the agent's memory (agent.toml is never rewritten) and re-syncs on the agent's own schedule; changing the journald filter aborts and respawns the source task since there's no other way to change what's being tailed. Building the hot-reload path surfaced a real, independent, pre-existing bug: shutdown was using poll_timeout(), which only drains once flush_interval has elapsed, silently dropping anything buffered more recently on every graceful shutdown that landed between flushes -- fixed with a new unconditional Batcher::flush_all(), now used at both shutdown and hot-reload. Verified live end-to-end against a real stack: an edited heartbeat interval changed a running agent's actual send cadence within one check-in cycle (confirmed by the real timestamps landing in ClickHouse), and an edited journald filter triggered a real source restart, both reflected back in the next reported-config snapshot. See /docs/agent-management-design.md.
131 lines
3.8 KiB
Go
131 lines
3.8 KiB
Go
// Package config loads ingest's configuration from environment variables.
|
|
// Phase 0 deliberately has no config file format of its own — env vars are
|
|
// enough for a docker-compose/k8s deployment and avoid pulling in a config
|
|
// library.
|
|
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
type Config struct {
|
|
GRPC GRPCConfig
|
|
TLS TLSConfig
|
|
Redpanda RedpandaConfig
|
|
ClickHouse ClickHouseConfig
|
|
Batch BatchConfig
|
|
// EnterpriseAuthURL enables per-tenant ingest credential validation
|
|
// (internal/grpcserver.TenantResolver) when set -- empty (the
|
|
// default) is a documented no-op, same "off unless configured" shape
|
|
// as every other optional enterprise integration point in this
|
|
// codebase (e.g. api's own ENTERPRISE_AUTH_URL).
|
|
EnterpriseAuthURL string
|
|
// AgentRegistry enables agent inventory/remote config
|
|
// (internal/agentregistry, internal/grpcserver.AgentRegistry) when
|
|
// Postgres.Addr is set -- same "off unless configured" shape as
|
|
// EnterpriseAuthURL above. Writes into the same sentry_metadata
|
|
// database api/web already use, via the same shared "sentry" role
|
|
// every other non-audit table in this schema uses (unlike
|
|
// audit_log's dedicated restricted role -- agent inventory carries
|
|
// no tamper-evidence requirement).
|
|
AgentRegistry AgentRegistryConfig
|
|
}
|
|
|
|
type AgentRegistryConfig struct {
|
|
Postgres PostgresConfig
|
|
}
|
|
|
|
type PostgresConfig struct {
|
|
Addr string
|
|
Database string
|
|
Username string
|
|
Password string
|
|
}
|
|
|
|
type GRPCConfig struct {
|
|
ListenAddr string
|
|
}
|
|
|
|
// TLSConfig is the server-side mTLS material: the ingest service's own
|
|
// cert/key, and the CA used to verify agent client certs.
|
|
type TLSConfig struct {
|
|
CertFile string
|
|
KeyFile string
|
|
ClientCAFile string
|
|
}
|
|
|
|
type RedpandaConfig struct {
|
|
Brokers []string
|
|
Topic string
|
|
ConsumerGroup string
|
|
}
|
|
|
|
type ClickHouseConfig struct {
|
|
Addr string
|
|
Database string
|
|
Username string
|
|
Password string
|
|
}
|
|
|
|
type BatchConfig struct {
|
|
MaxSize int
|
|
FlushIntervalMS int
|
|
}
|
|
|
|
func Load() (Config, error) {
|
|
cfg := Config{
|
|
GRPC: GRPCConfig{
|
|
ListenAddr: getenv("GRPC_LISTEN_ADDR", ":4317"),
|
|
},
|
|
TLS: TLSConfig{
|
|
CertFile: getenv("TLS_CERT_FILE", "/etc/sentry-ingest/server.pem"),
|
|
KeyFile: getenv("TLS_KEY_FILE", "/etc/sentry-ingest/server-key.pem"),
|
|
ClientCAFile: getenv("TLS_CLIENT_CA_FILE", "/etc/sentry-ingest/ca.pem"),
|
|
},
|
|
Redpanda: RedpandaConfig{
|
|
Brokers: strings.Split(getenv("REDPANDA_BROKERS", "localhost:9092"), ","),
|
|
Topic: getenv("REDPANDA_TOPIC", "sentry.logs.raw"),
|
|
ConsumerGroup: getenv("REDPANDA_CONSUMER_GROUP", "sentry-ingest"),
|
|
},
|
|
ClickHouse: ClickHouseConfig{
|
|
Addr: getenv("CLICKHOUSE_ADDR", "localhost:9000"),
|
|
Database: getenv("CLICKHOUSE_DATABASE", "sentry"),
|
|
Username: getenv("CLICKHOUSE_USERNAME", "default"),
|
|
Password: getenv("CLICKHOUSE_PASSWORD", ""),
|
|
},
|
|
EnterpriseAuthURL: getenv("ENTERPRISE_AUTH_URL", ""),
|
|
AgentRegistry: AgentRegistryConfig{
|
|
Postgres: PostgresConfig{
|
|
Addr: getenv("AGENT_REGISTRY_POSTGRES_ADDR", ""),
|
|
Database: getenv("AGENT_REGISTRY_POSTGRES_DATABASE", "sentry_metadata"),
|
|
Username: getenv("AGENT_REGISTRY_POSTGRES_USERNAME", "sentry"),
|
|
Password: getenv("AGENT_REGISTRY_POSTGRES_PASSWORD", ""),
|
|
},
|
|
},
|
|
}
|
|
|
|
maxSize, err := strconv.Atoi(getenv("CONSUMER_BATCH_MAX_SIZE", "500"))
|
|
if err != nil {
|
|
return Config{}, fmt.Errorf("CONSUMER_BATCH_MAX_SIZE: %w", err)
|
|
}
|
|
cfg.Batch.MaxSize = maxSize
|
|
|
|
flushMS, err := strconv.Atoi(getenv("CONSUMER_BATCH_FLUSH_INTERVAL_MS", "2000"))
|
|
if err != nil {
|
|
return Config{}, fmt.Errorf("CONSUMER_BATCH_FLUSH_INTERVAL_MS: %w", err)
|
|
}
|
|
cfg.Batch.FlushIntervalMS = flushMS
|
|
|
|
return cfg, nil
|
|
}
|
|
|
|
func getenv(key, fallback string) string {
|
|
if v := os.Getenv(key); v != "" {
|
|
return v
|
|
}
|
|
return fallback
|
|
}
|