Documentation
¶
Overview ¶
Package serviceconfig persists the structured YAML configuration sections (logger, carver, SAML, OIDC, metrics, TLS, etc.) per service into the database. Each row holds one section as a JSON-encoded blob, mirroring the YAML structure so it can be round-tripped back to YAML or rendered in the frontend.
Phase 1 is read-only: sections are seeded from the YAML file on first boot (create-if-missing) and served via GET endpoints. The YAML file remains the live source of truth. Phase 2 will allow editing editable sections from the API, and phase 3 will let services consume the DB values at startup so the YAML can shrink to connection-only settings.
Index ¶
- Constants
- Variables
- func CheckWritable(path string) (bool, string)
- type ConfigFileStatus
- type SectionSpec
- type ServiceConfig
- type ServiceConfigManager
- func (m *ServiceConfigManager) GetAll(envID uint) ([]ServiceConfig, error)
- func (m *ServiceConfigManager) GetAllByService(service string, envID uint) ([]ServiceConfig, error)
- func (m *ServiceConfigManager) GetFileStatus(service string) (ConfigFileStatus, error)
- func (m *ServiceConfigManager) GetSection(service, name string, envID uint) (ServiceConfig, error)
- func (m *ServiceConfigManager) HasPendingChanges(service string, envID uint) (bool, error)
- func (m *ServiceConfigManager) IsEditable(service, section string) bool
- func (m *ServiceConfigManager) PersistToFile(service, path string, cfg *config.ServiceParameters, envID uint) error
- func (m *ServiceConfigManager) ReportFile(service, path string) error
- func (m *ServiceConfigManager) Resolve(service string, cfg *config.ServiceParameters, envID uint) error
- func (m *ServiceConfigManager) Seed(service string, cfg *config.ServiceParameters, envID uint) error
- func (m *ServiceConfigManager) UpdateSection(service, name, value string, envID uint) (ServiceConfig, error)
- func (m *ServiceConfigManager) VerifySection(service, section string) bool
- func (m *ServiceConfigManager) VerifyService(service string) bool
Constants ¶
const NoEnvironmentID = 0
NoEnvironmentID is the sentinel environment ID for global (non-env-scoped) config rows. Mirrors settings.NoEnvironmentID.
const SourceDB string = "db"
SourceDB marks a row that has been edited through the API.
const SourceYAML string = "yaml"
SourceYAML marks a row seeded from the YAML file.
Variables ¶
var ErrConfigFileNotWritable = errors.New("configuration file is not writable")
ErrConfigFileNotWritable is returned when a persist is attempted against a configuration file the service process cannot write.
var ErrSectionNotEditable = fmt.Errorf("section is not editable")
ErrSectionNotEditable is returned when UpdateSection is called on a section that is not marked editable in the registry.
var SectionRegistry = map[string][]SectionSpec{ config.ServiceTLS: { {"service", true, "Core service listener, port, log level, auth mode"}, {"db", false, "Backend connection — not DB-editable"}, {"batchWriter", true, "DB batch writer tuning"}, {"redis", false, "Redis connection — not DB-editable"}, {"osquery", true, "osquery feature toggles"}, {"configEndpoints", false, "Config endpoint fan-out targets — contains secrets"}, {"osctrld", true, "osctrld integration"}, {"metrics", true, "Prometheus metrics endpoint"}, {"tls", false, "TLS termination certificate/key paths — not DB-editable"}, {"logger", false, "Log sinks — may contain credentials (future: editable)"}, {"carver", false, "File carver configuration — may contain credentials"}, {"debug", true, "HTTP debug dump settings"}, {"rateLimits", true, "HTTP request rate limits"}, }, config.ServiceAPI: { {"service", true, "Core service listener, port, log level, auth mode"}, {"db", false, "Backend connection — not DB-editable"}, {"redis", false, "Redis connection — not DB-editable"}, {"osquery", true, "osquery tables and feature toggles"}, {"saml", false, "SAML federated login configuration — not DB-editable"}, {"oidc", false, "OIDC federated login configuration — not DB-editable"}, {"jwt", false, "JWT signing configuration — not DB-editable"}, {"tls", false, "TLS termination certificate/key paths — not DB-editable"}, {"logger", false, "Log sinks — may contain credentials (future: editable)"}, {"carver", false, "File carver configuration — may contain credentials"}, {"debug", true, "HTTP debug dump settings"}, {"rateLimits", true, "HTTP request rate limits"}, }, }
SectionRegistry maps each service to the sections it owns. The order of the slice defines the display order in the frontend. Sections marked Editable=false (db, redis, tls, saml, oidc, jwt, and other connection / secret / auth-bearing sections) can never be written through the API.
Functions ¶
func CheckWritable ¶
CheckWritable reports whether the current process can write the given configuration file, and a human-readable reason when it cannot.
The check opens the file for writing without O_CREATE or O_TRUNC: that tests the permission the persist actually needs without touching the contents, and it is accurate where reasoning about mode bits, uid/gid and ACLs separately would not be.
Types ¶
type ConfigFileStatus ¶
type ConfigFileStatus struct {
gorm.Model
Service string `gorm:"uniqueIndex"`
Path string
Writable bool
Reason string
CheckedAt time.Time
}
ConfigFileStatus records where a service's YAML configuration file lives and whether the process running that service can write to it.
Each service reports its own row at boot. It cannot be reported centrally: osctrl-tls and osctrl-api run as separate processes, usually in separate containers with separate config volumes, so the API service can neither stat nor write the TLS service's file. The database is the only channel through which that fact travels — the same reason restarts are requested through pkg/servicecommands rather than performed directly.
type SectionSpec ¶
SectionSpec describes one section in the registry.
type ServiceConfig ¶
type ServiceConfig struct {
gorm.Model
Name string `gorm:"uniqueIndex:idx_service_config_unique"`
Service string `gorm:"uniqueIndex:idx_service_config_unique"`
EnvironmentID uint `gorm:"uniqueIndex:idx_service_config_unique"`
Type string // always "json" for now
Value string `gorm:"type:text"`
Source string // "yaml" or "db"
Editable bool
Info string
}
ServiceConfig stores one YAML configuration section for a service.
type ServiceConfigManager ¶
ServiceConfigManager manages the service_config table.
func NewServiceConfigManager ¶
func NewServiceConfigManager(backend *gorm.DB) *ServiceConfigManager
NewServiceConfigManager initializes the manager and auto-migrates the service_config table.
func (*ServiceConfigManager) GetAll ¶
func (m *ServiceConfigManager) GetAll(envID uint) ([]ServiceConfig, error)
GetAll retrieves all sections across all services.
func (*ServiceConfigManager) GetAllByService ¶
func (m *ServiceConfigManager) GetAllByService(service string, envID uint) ([]ServiceConfig, error)
GetAllByService retrieves all sections for a service.
func (*ServiceConfigManager) GetFileStatus ¶
func (m *ServiceConfigManager) GetFileStatus(service string) (ConfigFileStatus, error)
GetFileStatus retrieves the reported configuration file status of a service.
func (*ServiceConfigManager) GetSection ¶
func (m *ServiceConfigManager) GetSection(service, name string, envID uint) (ServiceConfig, error)
GetSection retrieves one section by service and name.
func (*ServiceConfigManager) HasPendingChanges ¶
func (m *ServiceConfigManager) HasPendingChanges(service string, envID uint) (bool, error)
HasPendingChanges reports whether any section of the service has been edited through the API and therefore no longer matches the YAML file on disk. A row with source=db is by definition a change the file does not have.
func (*ServiceConfigManager) IsEditable ¶
func (m *ServiceConfigManager) IsEditable(service, section string) bool
IsEditable checks that the section is registered and marked editable.
func (*ServiceConfigManager) PersistToFile ¶
func (m *ServiceConfigManager) PersistToFile(service, path string, cfg *config.ServiceParameters, envID uint) error
PersistToFile writes the effective configuration — the YAML file with the DB-edited sections overlaid — back to disk, then marks every section as being in sync with the file again.
cfg must be a FRESH load of the YAML file, never the running service's live ServiceParameters: Resolve mutates what it is given, so passing the live struct would apply the operator's pending edits to the running service without the restart they are supposed to go through.
func (*ServiceConfigManager) ReportFile ¶
func (m *ServiceConfigManager) ReportFile(service, path string) error
ReportFile records this service's configuration file path and whether the running process can write it. Services call this at boot, alongside Seed.
func (*ServiceConfigManager) Resolve ¶
func (m *ServiceConfigManager) Resolve(service string, cfg *config.ServiceParameters, envID uint) error
Resolve applies DB-edited sections back into ServiceParameters so the service uses the operator's DB values at runtime instead of the YAML defaults. For each section with source=db, the JSON value is unmarshaled into the matching ServiceParameters field, overriding the YAML value. Sections with source=yaml are skipped — the YAML value is already in ServiceParameters from the initial load.
This is the core of phase 3: the YAML file bootstraps the config, the DB overrides it for sections the operator has edited through the API.
func (*ServiceConfigManager) Seed ¶
func (m *ServiceConfigManager) Seed(service string, cfg *config.ServiceParameters, envID uint) error
Seed persists all sections from the YAML-loaded ServiceParameters into the database using create-if-missing semantics. If a row already exists for a (service, section, envID) tuple it is left untouched — the YAML never overwrites a DB-edited value. This makes seeding idempotent and safe to run on every boot.
func (*ServiceConfigManager) UpdateSection ¶
func (m *ServiceConfigManager) UpdateSection(service, name, value string, envID uint) (ServiceConfig, error)
UpdateSection replaces the JSON value of an editable section. It validates that the new value is valid JSON, that the section exists and is marked editable, and flips the source to SourceDB so subsequent boots won't clobber the change. Returns the updated row.
func (*ServiceConfigManager) VerifySection ¶
func (m *ServiceConfigManager) VerifySection(service, section string) bool
VerifySection checks that the section is registered for the given service.
func (*ServiceConfigManager) VerifyService ¶
func (m *ServiceConfigManager) VerifyService(service string) bool
VerifyService checks that the service is one of the known services.