2021-11-03 19:54:04 -06:00
|
|
|
package config
|
2019-11-19 10:00:20 -07:00
|
|
|
|
|
|
|
import (
|
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"testing"
|
|
|
|
"time"
|
2020-06-30 16:53:30 -06:00
|
|
|
|
2023-08-02 12:00:20 -06:00
|
|
|
"dario.cat/mergo"
|
2021-11-10 20:47:38 -07:00
|
|
|
"github.com/slackhq/nebula/test"
|
2020-06-30 16:53:30 -06:00
|
|
|
"github.com/stretchr/testify/assert"
|
2022-11-23 08:46:41 -07:00
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"gopkg.in/yaml.v2"
|
2019-11-19 10:00:20 -07:00
|
|
|
)
|
|
|
|
|
|
|
|
func TestConfig_Load(t *testing.T) {
|
2021-11-10 20:47:38 -07:00
|
|
|
l := test.NewLogger()
|
2019-11-19 10:00:20 -07:00
|
|
|
dir, err := ioutil.TempDir("", "config-test")
|
|
|
|
// invalid yaml
|
2021-11-03 19:54:04 -06:00
|
|
|
c := NewC(l)
|
2019-11-19 10:00:20 -07:00
|
|
|
ioutil.WriteFile(filepath.Join(dir, "01.yaml"), []byte(" invalid yaml"), 0644)
|
|
|
|
assert.EqualError(t, c.Load(dir), "yaml: unmarshal errors:\n line 1: cannot unmarshal !!str `invalid...` into map[interface {}]interface {}")
|
|
|
|
|
|
|
|
// simple multi config merge
|
2021-11-03 19:54:04 -06:00
|
|
|
c = NewC(l)
|
2019-11-19 10:00:20 -07:00
|
|
|
os.RemoveAll(dir)
|
|
|
|
os.Mkdir(dir, 0755)
|
|
|
|
|
|
|
|
assert.Nil(t, err)
|
|
|
|
|
|
|
|
ioutil.WriteFile(filepath.Join(dir, "01.yaml"), []byte("outer:\n inner: hi"), 0644)
|
|
|
|
ioutil.WriteFile(filepath.Join(dir, "02.yml"), []byte("outer:\n inner: override\nnew: hi"), 0644)
|
|
|
|
assert.Nil(t, c.Load(dir))
|
|
|
|
expected := map[interface{}]interface{}{
|
|
|
|
"outer": map[interface{}]interface{}{
|
|
|
|
"inner": "override",
|
|
|
|
},
|
|
|
|
"new": "hi",
|
|
|
|
}
|
|
|
|
assert.Equal(t, expected, c.Settings)
|
|
|
|
|
|
|
|
//TODO: test symlinked file
|
|
|
|
//TODO: test symlinked directory
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestConfig_Get(t *testing.T) {
|
2021-11-10 20:47:38 -07:00
|
|
|
l := test.NewLogger()
|
2019-11-19 10:00:20 -07:00
|
|
|
// test simple type
|
2021-11-03 19:54:04 -06:00
|
|
|
c := NewC(l)
|
2019-11-19 10:00:20 -07:00
|
|
|
c.Settings["firewall"] = map[interface{}]interface{}{"outbound": "hi"}
|
|
|
|
assert.Equal(t, "hi", c.Get("firewall.outbound"))
|
|
|
|
|
|
|
|
// test complex type
|
|
|
|
inner := []map[interface{}]interface{}{{"port": "1", "code": "2"}}
|
|
|
|
c.Settings["firewall"] = map[interface{}]interface{}{"outbound": inner}
|
|
|
|
assert.EqualValues(t, inner, c.Get("firewall.outbound"))
|
|
|
|
|
|
|
|
// test missing
|
|
|
|
assert.Nil(t, c.Get("firewall.nope"))
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestConfig_GetStringSlice(t *testing.T) {
|
2021-11-10 20:47:38 -07:00
|
|
|
l := test.NewLogger()
|
2021-11-03 19:54:04 -06:00
|
|
|
c := NewC(l)
|
2019-11-19 10:00:20 -07:00
|
|
|
c.Settings["slice"] = []interface{}{"one", "two"}
|
|
|
|
assert.Equal(t, []string{"one", "two"}, c.GetStringSlice("slice", []string{}))
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestConfig_GetBool(t *testing.T) {
|
2021-11-10 20:47:38 -07:00
|
|
|
l := test.NewLogger()
|
2021-11-03 19:54:04 -06:00
|
|
|
c := NewC(l)
|
2019-11-19 10:00:20 -07:00
|
|
|
c.Settings["bool"] = true
|
|
|
|
assert.Equal(t, true, c.GetBool("bool", false))
|
|
|
|
|
|
|
|
c.Settings["bool"] = "true"
|
|
|
|
assert.Equal(t, true, c.GetBool("bool", false))
|
|
|
|
|
|
|
|
c.Settings["bool"] = false
|
|
|
|
assert.Equal(t, false, c.GetBool("bool", true))
|
|
|
|
|
|
|
|
c.Settings["bool"] = "false"
|
|
|
|
assert.Equal(t, false, c.GetBool("bool", true))
|
|
|
|
|
|
|
|
c.Settings["bool"] = "Y"
|
|
|
|
assert.Equal(t, true, c.GetBool("bool", false))
|
|
|
|
|
|
|
|
c.Settings["bool"] = "yEs"
|
|
|
|
assert.Equal(t, true, c.GetBool("bool", false))
|
|
|
|
|
|
|
|
c.Settings["bool"] = "N"
|
|
|
|
assert.Equal(t, false, c.GetBool("bool", true))
|
|
|
|
|
|
|
|
c.Settings["bool"] = "nO"
|
|
|
|
assert.Equal(t, false, c.GetBool("bool", true))
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestConfig_HasChanged(t *testing.T) {
|
2021-11-10 20:47:38 -07:00
|
|
|
l := test.NewLogger()
|
2019-11-19 10:00:20 -07:00
|
|
|
// No reload has occurred, return false
|
2021-11-03 19:54:04 -06:00
|
|
|
c := NewC(l)
|
2019-11-19 10:00:20 -07:00
|
|
|
c.Settings["test"] = "hi"
|
|
|
|
assert.False(t, c.HasChanged(""))
|
|
|
|
|
|
|
|
// Test key change
|
2021-11-03 19:54:04 -06:00
|
|
|
c = NewC(l)
|
2019-11-19 10:00:20 -07:00
|
|
|
c.Settings["test"] = "hi"
|
|
|
|
c.oldSettings = map[interface{}]interface{}{"test": "no"}
|
|
|
|
assert.True(t, c.HasChanged("test"))
|
|
|
|
assert.True(t, c.HasChanged(""))
|
|
|
|
|
|
|
|
// No key change
|
2021-11-03 19:54:04 -06:00
|
|
|
c = NewC(l)
|
2019-11-19 10:00:20 -07:00
|
|
|
c.Settings["test"] = "hi"
|
|
|
|
c.oldSettings = map[interface{}]interface{}{"test": "hi"}
|
|
|
|
assert.False(t, c.HasChanged("test"))
|
|
|
|
assert.False(t, c.HasChanged(""))
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestConfig_ReloadConfig(t *testing.T) {
|
2021-11-10 20:47:38 -07:00
|
|
|
l := test.NewLogger()
|
2019-11-19 10:00:20 -07:00
|
|
|
done := make(chan bool, 1)
|
|
|
|
dir, err := ioutil.TempDir("", "config-test")
|
|
|
|
assert.Nil(t, err)
|
|
|
|
ioutil.WriteFile(filepath.Join(dir, "01.yaml"), []byte("outer:\n inner: hi"), 0644)
|
|
|
|
|
2021-11-03 19:54:04 -06:00
|
|
|
c := NewC(l)
|
2019-11-19 10:00:20 -07:00
|
|
|
assert.Nil(t, c.Load(dir))
|
|
|
|
|
|
|
|
assert.False(t, c.HasChanged("outer.inner"))
|
|
|
|
assert.False(t, c.HasChanged("outer"))
|
|
|
|
assert.False(t, c.HasChanged(""))
|
|
|
|
|
|
|
|
ioutil.WriteFile(filepath.Join(dir, "01.yaml"), []byte("outer:\n inner: ho"), 0644)
|
|
|
|
|
2021-11-03 19:54:04 -06:00
|
|
|
c.RegisterReloadCallback(func(c *C) {
|
2019-11-19 10:00:20 -07:00
|
|
|
done <- true
|
|
|
|
})
|
|
|
|
|
|
|
|
c.ReloadConfig()
|
|
|
|
assert.True(t, c.HasChanged("outer.inner"))
|
|
|
|
assert.True(t, c.HasChanged("outer"))
|
|
|
|
assert.True(t, c.HasChanged(""))
|
|
|
|
|
|
|
|
// Make sure we call the callbacks
|
|
|
|
select {
|
|
|
|
case <-done:
|
|
|
|
case <-time.After(1 * time.Second):
|
|
|
|
panic("timeout")
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
2022-11-23 08:46:41 -07:00
|
|
|
|
|
|
|
// Ensure mergo merges are done the way we expect.
|
|
|
|
// This is needed to test for potential regressions, like:
|
|
|
|
// - https://github.com/imdario/mergo/issues/187
|
|
|
|
func TestConfig_MergoMerge(t *testing.T) {
|
|
|
|
configs := [][]byte{
|
|
|
|
[]byte(`
|
|
|
|
listen:
|
|
|
|
port: 1234
|
|
|
|
`),
|
|
|
|
[]byte(`
|
|
|
|
firewall:
|
|
|
|
inbound:
|
|
|
|
- port: 443
|
|
|
|
proto: tcp
|
|
|
|
groups:
|
|
|
|
- server
|
|
|
|
- port: 443
|
|
|
|
proto: tcp
|
|
|
|
groups:
|
|
|
|
- webapp
|
|
|
|
`),
|
|
|
|
[]byte(`
|
|
|
|
listen:
|
|
|
|
host: 0.0.0.0
|
|
|
|
port: 4242
|
|
|
|
firewall:
|
|
|
|
outbound:
|
|
|
|
- port: any
|
|
|
|
proto: any
|
|
|
|
host: any
|
|
|
|
inbound:
|
|
|
|
- port: any
|
|
|
|
proto: icmp
|
|
|
|
host: any
|
|
|
|
`),
|
|
|
|
}
|
|
|
|
|
|
|
|
var m map[any]any
|
|
|
|
|
|
|
|
// merge the same way config.parse() merges
|
|
|
|
for _, b := range configs {
|
|
|
|
var nm map[any]any
|
|
|
|
err := yaml.Unmarshal(b, &nm)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
// We need to use WithAppendSlice so that firewall rules in separate
|
|
|
|
// files are appended together
|
|
|
|
err = mergo.Merge(&nm, m, mergo.WithAppendSlice)
|
|
|
|
m = nm
|
|
|
|
require.NoError(t, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
t.Logf("Merged Config: %#v", m)
|
|
|
|
mYaml, err := yaml.Marshal(m)
|
|
|
|
require.NoError(t, err)
|
|
|
|
t.Logf("Merged Config as YAML:\n%s", mYaml)
|
|
|
|
|
|
|
|
// If a bug is present, some items might be replaced instead of merged like we expect
|
|
|
|
expected := map[any]any{
|
|
|
|
"firewall": map[any]any{
|
|
|
|
"inbound": []any{
|
|
|
|
map[any]any{"host": "any", "port": "any", "proto": "icmp"},
|
|
|
|
map[any]any{"groups": []any{"server"}, "port": 443, "proto": "tcp"},
|
|
|
|
map[any]any{"groups": []any{"webapp"}, "port": 443, "proto": "tcp"}},
|
|
|
|
"outbound": []any{
|
|
|
|
map[any]any{"host": "any", "port": "any", "proto": "any"}}},
|
|
|
|
"listen": map[any]any{
|
|
|
|
"host": "0.0.0.0",
|
|
|
|
"port": 4242,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
assert.Equal(t, expected, m)
|
|
|
|
}
|