Add method to track and merge default interface blacklist entries

Introduce `mergeDefaultIFaceBlacklist` to ensure new defaults are appended to the interface blacklist while preserving user modifications. Add tests to verify behavior, including migration of old configs and handling of user-removed entries.
This commit is contained in:
Zoltán Papp
2026-03-09 16:48:14 +01:00
parent 09da089a90
commit 92d5418c02
2 changed files with 114 additions and 4 deletions

View File

@@ -100,6 +100,7 @@ type Config struct {
WgPort int
NetworkMonitor *bool
IFaceBlackList []string
IFaceBlackListAppliedDefaults []string `json:",omitempty"`
DisableIPv6Discovery bool
RosenpassEnabled bool
RosenpassPermissive bool
@@ -359,10 +360,7 @@ func (config *Config) apply(input ConfigInput) (updated bool, err error) {
updated = true
}
if len(config.IFaceBlackList) == 0 {
log.Infof("filling in interface blacklist with defaults: [ %s ]",
strings.Join(DefaultInterfaceBlacklist, " "))
config.IFaceBlackList = append(config.IFaceBlackList, DefaultInterfaceBlacklist...)
if changed := config.mergeDefaultIFaceBlacklist(); changed {
updated = true
}
@@ -596,6 +594,37 @@ func (config *Config) apply(input ConfigInput) (updated bool, err error) {
return updated, nil
}
// mergeDefaultIFaceBlacklist ensures that new entries added to DefaultInterfaceBlacklist
// are merged into an existing IFaceBlackList on upgrade, while respecting entries that
// the user deliberately removed. It tracks which defaults have been offered via
// IFaceBlackListAppliedDefaults so removals are not undone.
func (config *Config) mergeDefaultIFaceBlacklist() (updated bool) {
if len(config.IFaceBlackList) == 0 {
log.Infof("filling in interface blacklist with defaults: [ %s ]",
strings.Join(DefaultInterfaceBlacklist, " "))
config.IFaceBlackList = append(config.IFaceBlackList, DefaultInterfaceBlacklist...)
config.IFaceBlackListAppliedDefaults = append([]string{}, DefaultInterfaceBlacklist...)
return true
}
// Find defaults not yet tracked in AppliedDefaults — these are genuinely new.
// Entries already in AppliedDefaults were either kept or deliberately removed by the user.
newDefaults := util.SliceDiff(DefaultInterfaceBlacklist, config.IFaceBlackListAppliedDefaults)
if len(newDefaults) == 0 {
return false
}
// Only add entries not already present in the blacklist (avoid duplicates)
toAdd := util.SliceDiff(newDefaults, config.IFaceBlackList)
if len(toAdd) > 0 {
log.Infof("merging new default interface blacklist entries: [ %s ]",
strings.Join(toAdd, " "))
config.IFaceBlackList = append(config.IFaceBlackList, toAdd...)
}
config.IFaceBlackListAppliedDefaults = append(config.IFaceBlackListAppliedDefaults, newDefaults...)
return true
}
// parseURL parses and validates a service URL
func parseURL(serviceName, serviceURL string) (*url.URL, error) {
parsedMgmtURL, err := url.ParseRequestURI(serviceURL)

View File

@@ -108,6 +108,87 @@ func TestExtraIFaceBlackList(t *testing.T) {
assert.Contains(t, readConf.(*Config).IFaceBlackList, "eth1")
}
func TestIFaceBlackListMigratesNewDefaults(t *testing.T) {
tempDir := t.TempDir()
configPath := filepath.Join(tempDir, "config.json")
// Create a config that simulates an old install with a partial IFaceBlackList
// (missing the newer CNI entries like "cilium_", "cali", etc.)
config, err := UpdateOrCreateConfig(ConfigInput{
ConfigPath: configPath,
})
require.NoError(t, err)
// Simulate an old config that predates AppliedDefaults tracking:
// it has only the original entries, no CNI prefixes, and no AppliedDefaults.
oldList := []string{iface.WgInterfaceDefault, "wt", "utun", "tun0", "zt", "ZeroTier", "wg", "ts",
"Tailscale", "tailscale", "docker", "veth", "br-", "lo"}
config.IFaceBlackList = oldList
config.IFaceBlackListAppliedDefaults = nil
err = WriteOutConfig(configPath, config)
require.NoError(t, err)
// Re-read the config — apply() should merge in missing defaults
reloaded, err := GetConfig(configPath)
require.NoError(t, err)
for _, entry := range DefaultInterfaceBlacklist {
assert.Contains(t, reloaded.IFaceBlackList, entry,
"IFaceBlackList should contain default entry %q after migration", entry)
}
// Verify no duplicates were introduced
seen := make(map[string]bool)
for _, entry := range reloaded.IFaceBlackList {
assert.False(t, seen[entry], "duplicate entry %q in IFaceBlackList", entry)
seen[entry] = true
}
// AppliedDefaults should now track all current defaults
for _, entry := range DefaultInterfaceBlacklist {
assert.Contains(t, reloaded.IFaceBlackListAppliedDefaults, entry,
"AppliedDefaults should track %q", entry)
}
// Re-read again — should not change (idempotent)
reloaded2, err := GetConfig(configPath)
require.NoError(t, err)
assert.Equal(t, reloaded.IFaceBlackList, reloaded2.IFaceBlackList,
"IFaceBlackList should be stable on subsequent reads")
}
func TestIFaceBlackListRespectsUserRemoval(t *testing.T) {
tempDir := t.TempDir()
configPath := filepath.Join(tempDir, "config.json")
// Create a fresh config (all defaults applied)
config, err := UpdateOrCreateConfig(ConfigInput{
ConfigPath: configPath,
})
require.NoError(t, err)
require.Contains(t, config.IFaceBlackList, "cali")
// User deliberately removes "cali" from their blacklist
filtered := make([]string, 0, len(config.IFaceBlackList))
for _, entry := range config.IFaceBlackList {
if entry != "cali" {
filtered = append(filtered, entry)
}
}
config.IFaceBlackList = filtered
err = WriteOutConfig(configPath, config)
require.NoError(t, err)
// Re-read — "cali" should NOT be re-added because it's in AppliedDefaults
reloaded, err := GetConfig(configPath)
require.NoError(t, err)
assert.NotContains(t, reloaded.IFaceBlackList, "cali",
"user-removed entry should not be re-added")
// AppliedDefaults should still contain "cali" (it was offered)
assert.Contains(t, reloaded.IFaceBlackListAppliedDefaults, "cali")
}
func TestHiddenPreSharedKey(t *testing.T) {
hidden := "**********"
samplePreSharedKey := "mysecretpresharedkey"