Make nebula config generation deterministic

This commit is contained in:
Brian Picciano 2024-12-12 20:51:13 +01:00
parent e750bc44a5
commit b4a58d1508
3 changed files with 73 additions and 12 deletions

View File

@ -8,6 +8,7 @@ import (
"isle/daemon/daecommon" "isle/daemon/daecommon"
"isle/nebula" "isle/nebula"
"isle/toolkit" "isle/toolkit"
"isle/yamlutil"
"net" "net"
"path/filepath" "path/filepath"
"strconv" "strconv"
@ -51,19 +52,18 @@ func waitForNebula(
return ctx.Err() return ctx.Err()
} }
// TODO this needs to be produce a deterministic config value.
func nebulaConfig( func nebulaConfig(
networkConfig daecommon.NetworkConfig, networkConfig daecommon.NetworkConfig,
hostBootstrap bootstrap.Bootstrap, hostBootstrap bootstrap.Bootstrap,
) ( ) (
map[string]any, error, any, error,
) { ) {
var ( var (
lighthouseHostIPs []string lighthouseHostIPs []string
staticHostMap = map[string][]string{} staticHostMap = yamlutil.OrderedMap[string, []string]{}
) )
for _, host := range hostBootstrap.Hosts { for _, host := range hostBootstrap.HostsOrdered() {
if host.Nebula.PublicAddr == "" { if host.Nebula.PublicAddr == "" {
continue continue
@ -105,18 +105,20 @@ func nebulaConfig(
) )
} }
config := map[string]any{ type m = yamlutil.OrderedMap[string, any]
"pki": map[string]string{
config := m{
"pki": m{
"ca": string(caCertPEM), "ca": string(caCertPEM),
"cert": string(hostCertPEM), "cert": string(hostCertPEM),
"key": string(hostKeyPEM), "key": string(hostKeyPEM),
}, },
"static_host_map": staticHostMap, "static_host_map": staticHostMap,
"punchy": map[string]bool{ "punchy": m{
"punch": true, "punch": true,
"respond": true, "respond": true,
}, },
"tun": map[string]any{ "tun": m{
"dev": nebula.GetDeviceName(hostBootstrap.NetworkCreationParams.ID), "dev": nebula.GetDeviceName(hostBootstrap.NetworkCreationParams.ID),
}, },
"firewall": firewall, "firewall": firewall,
@ -124,12 +126,12 @@ func nebulaConfig(
if publicAddr := networkConfig.VPN.PublicAddr; publicAddr == "" { if publicAddr := networkConfig.VPN.PublicAddr; publicAddr == "" {
config["listen"] = map[string]string{ config["listen"] = m{
"host": "0.0.0.0", "host": "0.0.0.0",
"port": "0", "port": "0",
} }
config["lighthouse"] = map[string]any{ config["lighthouse"] = m{
"hosts": lighthouseHostIPs, "hosts": lighthouseHostIPs,
} }
@ -149,12 +151,12 @@ func nebulaConfig(
host = "0.0.0.0" host = "0.0.0.0"
} }
config["listen"] = map[string]string{ config["listen"] = m{
"host": host, "host": host,
"port": port, "port": port,
} }
config["lighthouse"] = map[string]any{ config["lighthouse"] = m{
"hosts": []string{}, "hosts": []string{},
"am_lighthouse": true, "am_lighthouse": true,
} }

View File

@ -0,0 +1,39 @@
package yamlutil
import (
"cmp"
"slices"
"gopkg.in/yaml.v3"
)
// OrderedMap is like a normal map, except that when it is marshaled to yaml it
// will do so with its keys in ascending order. This makes it useful when
// generating output which needs to be deterministic.
type OrderedMap[K comparable, V any] map[K]V
func (m OrderedMap[K, V]) MarshalYAML() (any, error) {
type wrapped map[K]V
var n yaml.Node
if err := n.Encode(wrapped(m)); err != nil {
return nil, err
}
pairs := make([][2]*yaml.Node, len(n.Content)/2)
for i := range pairs {
pairs[i][0] = n.Content[i*2]
pairs[i][1] = n.Content[i*2+1]
}
slices.SortFunc(pairs, func(a, b [2]*yaml.Node) int {
return cmp.Compare(a[0].Value, b[0].Value)
})
for i := range pairs {
n.Content[i*2] = pairs[i][0]
n.Content[i*2+1] = pairs[i][1]
}
return n, nil
}

View File

@ -0,0 +1,20 @@
package yamlutil
import (
"testing"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
)
func TestOrderedMap(t *testing.T) {
m := OrderedMap[string, int]{
"a": 1,
"b": 2,
"c": 3,
}
b, err := yaml.Marshal(m)
assert.NoError(t, err)
assert.Equal(t, "a: 1\nb: 2\nc: 3\n", string(b))
}