diff --git a/service/internal/config/config.go b/service/internal/config/config.go index 4b15077..5abc54e 100644 --- a/service/internal/config/config.go +++ b/service/internal/config/config.go @@ -40,7 +40,7 @@ type Action struct { Groups []string `koanf:"groups"` Justification string `koanf:"justification"` // SourceFile is set by OliveTin when loading config (not user YAML). - SourceFile string `koanf:"x-olivetin-source-file"` + SourceFile string `koanf:"-"` } func (action *Action) RequiresJustification() bool { @@ -113,7 +113,7 @@ type EntityFile struct { Icon string `koanf:"icon"` Properties []EntityProperty `koanf:"properties"` // SourceFile is set by OliveTin when loading config (not user YAML). - SourceFile string `koanf:"x-olivetin-source-file"` + SourceFile string `koanf:"-"` } // EntityProperty defines a column shown when listing entity instances in the UI. diff --git a/service/internal/config/config_reloader.go b/service/internal/config/config_reloader.go index 39a2484..0927387 100644 --- a/service/internal/config/config_reloader.go +++ b/service/internal/config/config_reloader.go @@ -50,6 +50,7 @@ func AppendSource(cfg *Config, k *koanf.Koanf, configPath string) { return } + applyStampedSourceFiles(k, cfg) afterLoadFinalize(cfg, configPath) } diff --git a/service/internal/config/source_file.go b/service/internal/config/source_file.go index 817ec52..37f1880 100644 --- a/service/internal/config/source_file.go +++ b/service/internal/config/source_file.go @@ -8,7 +8,8 @@ import ( const sourceFileKey = "x-olivetin-source-file" // stampSourceOnMaps sets the OliveTin source-file marker on each map in a -// koanf slice value (actions or entities). +// koanf slice value (actions or entities). Always overwrites so user-provided +// x-olivetin-source-file values cannot spoof the real config path. func stampSourceOnMaps(raw any, sourceFile string) any { if sourceFile == "" { return raw @@ -30,9 +31,6 @@ func stampSourceOnMap(item any, sourceFile string) { if !ok { return } - if existing, ok := m[sourceFileKey].(string); ok && existing != "" { - return - } m[sourceFileKey] = sourceFile } @@ -49,3 +47,50 @@ func stampConfigKey(k *koanf.Koanf, key, configPath string) { }).Errorf("Failed to persist source stamps: %v", err) } } + +// applyStampedSourceFiles copies stamped source paths from koanf maps onto +// unmarshaled actions/entities. SourceFile uses koanf:"-" so YAML cannot set it. +func applyStampedSourceFiles(k *koanf.Koanf, cfg *Config) { + actionPaths := stampedSourcePaths(k.Get("actions")) + for i, action := range cfg.Actions { + if action != nil { + action.SourceFile = sourcePathAt(actionPaths, i) + } + } + + entityPaths := stampedSourcePaths(k.Get("entities")) + for i, entity := range cfg.Entities { + if entity != nil { + entity.SourceFile = sourcePathAt(entityPaths, i) + } + } +} + +func sourcePathAt(paths []string, index int) string { + if index >= len(paths) { + return "" + } + return paths[index] +} + +func stampedSourcePaths(raw any) []string { + items, ok := raw.([]interface{}) + if !ok { + return nil + } + + paths := make([]string, len(items)) + for i, item := range items { + paths[i] = stampedSourceFromMap(item) + } + return paths +} + +func stampedSourceFromMap(item any) string { + m, ok := item.(map[string]interface{}) + if !ok { + return "" + } + path, _ := m[sourceFileKey].(string) + return path +} diff --git a/service/internal/config/source_file_test.go b/service/internal/config/source_file_test.go index a237c55..25e954b 100644 --- a/service/internal/config/source_file_test.go +++ b/service/internal/config/source_file_test.go @@ -49,3 +49,32 @@ entities: require.Len(t, cfg.Entities, 1) assert.Equal(t, includePath, cfg.Entities[0].SourceFile) } + +func TestAppendSourceIgnoresUserProvidedSourceFile(t *testing.T) { + dir := t.TempDir() + basePath := filepath.Join(dir, "config.yaml") + require.NoError(t, os.WriteFile(basePath, []byte(` +actions: + - title: Spoofed + shell: echo hi + x-olivetin-source-file: /tmp/fake-user-path.yaml +entities: + - name: host + file: hosts.yaml + x-olivetin-source-file: /tmp/fake-entity-path.yaml +`), 0o644)) + + k := koanf.New(".") + require.NoError(t, k.Load(file.Provider(basePath), yaml.Parser())) + + cfg := config.DefaultConfig() + config.AppendSource(cfg, k, basePath) + + require.Len(t, cfg.Actions, 1) + assert.Equal(t, basePath, cfg.Actions[0].SourceFile) + assert.NotEqual(t, "/tmp/fake-user-path.yaml", cfg.Actions[0].SourceFile) + + require.Len(t, cfg.Entities, 1) + assert.Equal(t, basePath, cfg.Entities[0].SourceFile) + assert.NotEqual(t, "/tmp/fake-entity-path.yaml", cfg.Entities[0].SourceFile) +}