Implement SetConfig, but in a stupid way

This commit is contained in:
Brian Picciano 2024-10-24 19:52:08 +02:00
parent 63cefd403e
commit 5c41cedea3
11 changed files with 288 additions and 121 deletions

View File

@ -124,3 +124,13 @@ func (c *rpcClient) RemoveHost(ctx context.Context, hostName nebula.HostName) (e
) )
return return
} }
func (c *rpcClient) SetConfig(ctx context.Context, n1 daecommon.NetworkConfig) (err error) {
err = c.client.Call(
ctx,
nil,
"SetConfig",
n1,
)
return
}

View File

@ -239,10 +239,8 @@ func LoadConfig(userConfigPath string) (Config, error) {
// corresponds with the given alloc from the daemon config. This will panic if // corresponds with the given alloc from the daemon config. This will panic if
// no associated instance can be found. // no associated instance can be found.
func BootstrapGarageHostForAlloc( func BootstrapGarageHostForAlloc(
host bootstrap.Host, host bootstrap.Host, alloc ConfigStorageAllocation,
alloc ConfigStorageAllocation,
) bootstrap.GarageHostInstance { ) bootstrap.GarageHostInstance {
for _, inst := range host.Garage.Instances { for _, inst := range host.Garage.Instances {
if inst.RPCPort == alloc.RPCPort { if inst.RPCPort == alloc.RPCPort {
return inst return inst

View File

@ -457,6 +457,21 @@ func (d *Daemon) GetConfig(
) )
} }
func (d *Daemon) SetConfig(
ctx context.Context, config daecommon.NetworkConfig,
) error {
_, err := withNetwork(
ctx,
d,
func(ctx context.Context, n network.Network) (struct{}, error) {
// TODO needs to check that public addresses aren't being shared
// across networks, and whatever else happens in Config.Validate.
return struct{}{}, n.SetConfig(ctx, config)
},
)
return err
}
// Shutdown blocks until all resources held or created by the daemon, // Shutdown blocks until all resources held or created by the daemon,
// including child processes it has started, have been cleaned up. // including child processes it has started, have been cleaned up.
// //

View File

@ -113,6 +113,10 @@ type RPC interface {
// GetConfig returns the configuration currently in use. // GetConfig returns the configuration currently in use.
GetConfig(context.Context) (daecommon.NetworkConfig, error) GetConfig(context.Context) (daecommon.NetworkConfig, error)
// SetConfig overrides the current config with the given one, adjusting any
// running child processes as needed.
SetConfig(context.Context, daecommon.NetworkConfig) error
} }
// Network manages membership in a single micropelago network. Each Network // Network manages membership in a single micropelago network. Each Network
@ -145,12 +149,19 @@ type Network interface {
// Network instance. A nil Opts is equivalent to a zero value. // Network instance. A nil Opts is equivalent to a zero value.
type Opts struct { type Opts struct {
ChildrenOpts *children.Opts ChildrenOpts *children.Opts
GarageAdminToken string // Will be randomly generated if left unset.
} }
func (o *Opts) withDefaults() *Opts { func (o *Opts) withDefaults() *Opts {
if o == nil { if o == nil {
o = new(Opts) o = new(Opts)
} }
if o.GarageAdminToken == "" {
o.GarageAdminToken = toolkit.RandStr(32)
}
return o return o
} }
@ -165,7 +176,6 @@ type network struct {
opts *Opts opts *Opts
secretsStore secrets.Store secretsStore secrets.Store
garageAdminToken string
l sync.RWMutex l sync.RWMutex
children *children.Children children *children.Children
@ -192,7 +202,6 @@ func instatiateNetwork(
stateDir: stateDir, stateDir: stateDir,
runtimeDir: runtimeDir, runtimeDir: runtimeDir,
opts: opts.withDefaults(), opts: opts.withDefaults(),
garageAdminToken: toolkit.RandStr(32),
shutdownCh: make(chan struct{}), shutdownCh: make(chan struct{}),
} }
} }
@ -427,8 +436,8 @@ func (n *network) initialize(
n.secretsStore, n.secretsStore,
n.networkConfig, n.networkConfig,
n.runtimeDir, n.runtimeDir,
n.garageAdminToken, n.opts.GarageAdminToken,
currBootstrap, n.currBootstrap,
n.opts.ChildrenOpts, n.opts.ChildrenOpts,
) )
if err != nil { if err != nil {
@ -443,6 +452,14 @@ func (n *network) initialize(
return fmt.Errorf("performing post-initialization: %w", err) return fmt.Errorf("performing post-initialization: %w", err)
} }
// Do this now so that everything is stable before returning. This also
// serves a dual-purpose, as it makes sure that the PUT from the postInit
// above has propagated from the local garage instance, if there is one.
n.logger.Info(ctx, "Reloading bootstrap from network")
if err = n.reload(ctx); err != nil {
return fmt.Errorf("Reloading network bootstrap: %w", err)
}
// TODO annotate this context with creation params // TODO annotate this context with creation params
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
n.wg.Add(1) n.wg.Add(1)
@ -466,7 +483,11 @@ func (n *network) postInit(ctx context.Context) error {
if len(n.networkConfig.Storage.Allocations) > 0 { if len(n.networkConfig.Storage.Allocations) > 0 {
n.logger.Info(ctx, "Applying garage layout") n.logger.Info(ctx, "Applying garage layout")
if err := garageApplyLayout( if err := garageApplyLayout(
ctx, n.logger, n.networkConfig, n.garageAdminToken, n.currBootstrap, ctx,
n.logger,
n.networkConfig,
n.opts.GarageAdminToken,
n.currBootstrap,
); err != nil { ); err != nil {
return fmt.Errorf("applying garage layout: %w", err) return fmt.Errorf("applying garage layout: %w", err)
} }
@ -486,7 +507,7 @@ func (n *network) postInit(ctx context.Context) error {
ctx, ctx,
n.logger, n.logger,
n.networkConfig, n.networkConfig,
n.garageAdminToken, n.opts.GarageAdminToken,
n.currBootstrap, n.currBootstrap,
) )
if err != nil { if err != nil {
@ -506,14 +527,6 @@ func (n *network) postInit(ctx context.Context) error {
return fmt.Errorf("updating host info in garage: %w", err) return fmt.Errorf("updating host info in garage: %w", err)
} }
// Do this now so that everything is stable before returning. This also
// serves a dual-purpose, as it makes sure that the PUT above has propagated
// from the local garage instance, if there is one.
n.logger.Info(ctx, "Reloading bootstrap from network")
if err = n.reload(ctx); err != nil {
return fmt.Errorf("Reloading network bootstrap: %w", err)
}
return nil return nil
} }
@ -622,6 +635,7 @@ func (n *network) getBootstrap() (
} }
func (n *network) GetHosts(ctx context.Context) ([]bootstrap.Host, error) { func (n *network) GetHosts(ctx context.Context) ([]bootstrap.Host, error) {
// TODO use withCurrBootstrap in here
b, err := n.getBootstrap() b, err := n.getBootstrap()
if err != nil { if err != nil {
return nil, fmt.Errorf("retrieving bootstrap: %w", err) return nil, fmt.Errorf("retrieving bootstrap: %w", err)
@ -887,6 +901,55 @@ func (n *network) GetConfig(context.Context) (daecommon.NetworkConfig, error) {
return n.networkConfig, nil return n.networkConfig, nil
} }
func (n *network) SetConfig(
ctx context.Context, config daecommon.NetworkConfig,
) error {
newBootstrap, err := coalesceNetworkConfigAndBootstrap(
config, n.currBootstrap,
)
if err != nil {
return fmt.Errorf("combining configuration into bootstrap: %w", err)
}
n.l.Lock()
defer n.l.Unlock()
n.logger.Info(ctx, "Shutting down children")
n.children.Shutdown()
err = writeBootstrapToStateDir(n.stateDir.Path, newBootstrap)
if err != nil {
return fmt.Errorf("writing bootstrap to state dir: %w", err)
}
n.networkConfig = config
n.currBootstrap = newBootstrap
n.logger.Info(ctx, "Creating child processes")
n.children, err = children.New(
ctx,
n.logger.WithNamespace("children"),
n.envBinDirPath,
n.secretsStore,
n.networkConfig,
n.runtimeDir,
n.opts.GarageAdminToken,
n.currBootstrap,
n.opts.ChildrenOpts,
)
if err != nil {
return fmt.Errorf("creating child processes: %w", err)
}
n.logger.Info(ctx, "Child processes re-created")
if err := n.postInit(ctx); err != nil {
return fmt.Errorf("performing post-initialization: %w", err)
}
return nil
}
func (n *network) GetNetworkCreationParams( func (n *network) GetNetworkCreationParams(
ctx context.Context, ctx context.Context,
) ( ) (

View File

@ -1,8 +1,14 @@
package network package network
import ( import (
"reflect" "isle/bootstrap"
"isle/daemon/daecommon"
"isle/garage"
"isle/jsonutil"
"isle/nebula"
"testing" "testing"
"github.com/stretchr/testify/assert"
) )
func TestCreate(t *testing.T) { func TestCreate(t *testing.T) {
@ -12,15 +18,8 @@ func TestCreate(t *testing.T) {
) )
gotCreationParams, err := LoadCreationParams(network.stateDir) gotCreationParams, err := LoadCreationParams(network.stateDir)
if err != nil { assert.NoError(t, err)
t.Fatalf("calling LoadCreationParams: %v", err) assert.Equal(t, gotCreationParams, network.creationParams)
} else if network.creationParams != gotCreationParams {
t.Fatalf(
"expected CreationParams %+v, got %+v",
network.creationParams,
gotCreationParams,
)
}
} }
func TestLoad(t *testing.T) { func TestLoad(t *testing.T) {
@ -32,9 +31,7 @@ func TestLoad(t *testing.T) {
) )
t.Log("Shutting down network") t.Log("Shutting down network")
if err := network.Shutdown(); err != nil { assert.NoError(t, network.Shutdown())
t.Fatal(err)
}
t.Log("Calling Load") t.Log("Calling Load")
loadedNetwork, err := Load( loadedNetwork, err := Load(
@ -46,14 +43,11 @@ func TestLoad(t *testing.T) {
h.mkDir(t, "runtime"), h.mkDir(t, "runtime"),
network.opts, network.opts,
) )
if err != nil { assert.NoError(t, err)
t.Fatalf("Load failed: %v", err)
}
t.Cleanup(func() { t.Cleanup(func() {
t.Log("Shutting down loadedNetwork") t.Log("Shutting down loadedNetwork")
if err := loadedNetwork.Shutdown(); err != nil { assert.NoError(t, loadedNetwork.Shutdown())
t.Logf("Shutting down loadedNetwork failed: %v", err)
}
}) })
} }
@ -65,22 +59,12 @@ func TestJoin(t *testing.T) {
) )
primusHosts, err := primus.GetHosts(h.ctx) primusHosts, err := primus.GetHosts(h.ctx)
if err != nil { assert.NoError(t, err)
t.Fatalf("getting hosts from primus: %v", err)
}
secondusHosts, err := secondus.GetHosts(h.ctx) secondusHosts, err := secondus.GetHosts(h.ctx)
if err != nil { assert.NoError(t, err)
t.Fatalf("getting hosts from secondus: %v", err)
}
if !reflect.DeepEqual(primusHosts, secondusHosts) { assert.Equal(t, primusHosts, secondusHosts)
t.Fatalf(
"expected primusHosts %+v to equal secondusHosts %+v",
primusHosts,
secondusHosts,
)
}
} }
func TestNetwork_GetConfig(t *testing.T) { func TestNetwork_GetConfig(t *testing.T) {
@ -90,17 +74,83 @@ func TestNetwork_GetConfig(t *testing.T) {
) )
config, err := network.GetConfig(h.ctx) config, err := network.GetConfig(h.ctx)
if err != nil { assert.NoError(t, err)
t.Fatal(err)
assert.Equal(t, config, network.networkConfig)
} }
if !reflect.DeepEqual(config, network.networkConfig) { func TestNetwork_SetConfig(t *testing.T) {
t.Fatalf( t.Run("adding storage alloc", func(t *testing.T) {
"Config doesn't match the one used to create the Network\n"+ var (
"exp: %+v\n"+ h = newIntegrationHarness(t)
"got: %+v", network = h.createNetwork(t, "primus", nil)
config,
network.networkConfig,
) )
network.networkConfig.Storage.Allocations = append(
network.networkConfig.Storage.Allocations,
daecommon.ConfigStorageAllocation{
DataPath: h.mkDir(t, "data").Path,
MetaPath: h.mkDir(t, "meta").Path,
Capacity: 1,
S3APIPort: 4900,
RPCPort: 4901,
AdminPort: 4902,
},
)
assert.NoError(t, network.SetConfig(h.ctx, network.networkConfig))
// Check that the Host information was updated
newHosts, err := network.GetHosts(h.ctx)
assert.NoError(t, err)
newHostsByName := map[nebula.HostName]bootstrap.Host{}
for _, h := range newHosts {
newHostsByName[h.Name] = h
}
newHost, ok := newHostsByName[network.hostName]
assert.True(t, ok)
allocs := newHost.HostConfigured.Garage.Instances
assert.Len(t, allocs, 4)
newAlloc := allocs[3]
assert.NotEmpty(t, newAlloc.ID)
newAlloc.ID = ""
assert.Equal(t, bootstrap.GarageHostInstance{
S3APIPort: 4900,
RPCPort: 4901,
}, newAlloc)
// Check that the bootstrap file was written with the new host config
var storedBootstrap bootstrap.Bootstrap
assert.NoError(t, jsonutil.LoadFile(
&storedBootstrap, bootstrap.StateDirPath(network.stateDir.Path),
))
assert.Equal(t, newHostsByName, storedBootstrap.Hosts)
// Check that garage layout contains the new allocation
garageAdminClient := newGarageAdminClient(
h.logger,
network.networkConfig,
network.opts.GarageAdminToken,
storedBootstrap,
)
layout, err := garageAdminClient.GetLayout(h.ctx)
assert.NoError(t, err)
expPeers := make([]garage.PeerLayout, len(allocs))
for i := range allocs {
expPeers[i] = garage.PeerLayout{
ID: allocs[i].ID,
Capacity: 1_000_000_000,
Zone: string(network.hostName),
Tags: []string{},
} }
} }
assert.ElementsMatch(t, expPeers, layout.Peers)
})
}

View File

@ -17,6 +17,8 @@ import (
"testing" "testing"
"dev.mediocregopher.com/mediocre-go-lib.git/mlog" "dev.mediocregopher.com/mediocre-go-lib.git/mlog"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
@ -83,9 +85,8 @@ func newIntegrationHarness(t *testing.T) *integrationHarness {
toolkit.MarkIntegrationTest(t) toolkit.MarkIntegrationTest(t)
rootDir, err := os.MkdirTemp("", "isle-network-it-test.*") rootDir, err := os.MkdirTemp("", "isle-network-it-test.*")
if err != nil { require.NoError(t, err)
t.Fatalf("creating root temp dir: %v", err)
}
t.Logf("Temporary test directory: %q", rootDir) t.Logf("Temporary test directory: %q", rootDir)
t.Cleanup(func() { t.Cleanup(func() {
@ -95,14 +96,14 @@ func newIntegrationHarness(t *testing.T) *integrationHarness {
} }
t.Logf("Deleting temp directory %q", rootDir) t.Logf("Deleting temp directory %q", rootDir)
if err := os.RemoveAll(rootDir); err != nil { assert.NoError(t, os.RemoveAll(rootDir))
t.Errorf("failed to remove %q: %v", rootDir, err)
}
}) })
return &integrationHarness{ return &integrationHarness{
ctx: context.Background(), ctx: context.Background(),
logger: mlog.NewTestLogger(t), logger: mlog.NewLogger(&mlog.LoggerOpts{
MessageHandler: mlog.NewTestMessageHandler(t),
}),
rootDir: toolkit.Dir{Path: rootDir}, rootDir: toolkit.Dir{Path: rootDir},
} }
} }
@ -112,9 +113,7 @@ func (h *integrationHarness) mkDir(t *testing.T, name string) toolkit.Dir {
t.Logf("Creating directory %q", fullName) t.Logf("Creating directory %q", fullName)
d, err := h.rootDir.MkChildDir(fullName, false) d, err := h.rootDir.MkChildDir(fullName, false)
if err != nil { require.NoError(t, err)
t.Fatalf("creating %q: %v", fullName, err)
}
return d return d
} }
@ -147,9 +146,7 @@ func (h *integrationHarness) mkNetworkConfig(
} }
allocsJSON, err := json.Marshal(allocs) allocsJSON, err := json.Marshal(allocs)
if err != nil { require.NoError(t, err)
t.Fatalf("marshaling allocs: %v", err)
}
return mustParseNetworkConfigf(` return mustParseNetworkConfigf(`
vpn: vpn:
@ -174,13 +171,10 @@ func (h *integrationHarness) mkChildrenOpts(
) )
childrenLogFile, err := os.Create(childrenLogFilePath) childrenLogFile, err := os.Create(childrenLogFilePath)
if err != nil { require.NoError(t, err)
t.Fatalf("creating %q: %v", childrenLogFilePath, err)
}
t.Cleanup(func() { t.Cleanup(func() {
if err := childrenLogFile.Close(); err != nil { assert.NoError(t, err)
t.Errorf("closing %q: %v", childrenLogFilePath, err)
}
}) })
if os.Getenv("ISLE_INTEGRATION_TEST_CHILDREN_LOG_STDOUT") == "" { if os.Getenv("ISLE_INTEGRATION_TEST_CHILDREN_LOG_STDOUT") == "" {
@ -218,6 +212,7 @@ func (o *createNetworkOpts) withDefaults() *createNetworkOpts {
type integrationHarnessNetwork struct { type integrationHarnessNetwork struct {
Network Network
hostName nebula.HostName
creationParams bootstrap.CreationParams creationParams bootstrap.CreationParams
networkConfig daecommon.NetworkConfig networkConfig daecommon.NetworkConfig
stateDir, runtimeDir toolkit.Dir stateDir, runtimeDir toolkit.Dir
@ -246,6 +241,7 @@ func (h *integrationHarness) createNetwork(
networkOpts = &Opts{ networkOpts = &Opts{
ChildrenOpts: h.mkChildrenOpts(t, runtimeDir), ChildrenOpts: h.mkChildrenOpts(t, runtimeDir),
GarageAdminToken: "admin_token",
} }
) )
@ -276,6 +272,7 @@ func (h *integrationHarness) createNetwork(
return integrationHarnessNetwork{ return integrationHarnessNetwork{
network, network,
hostName,
opts.creationParams, opts.creationParams,
networkConfig, networkConfig,
stateDir, stateDir,
@ -306,6 +303,7 @@ func (h *integrationHarness) joinNetwork(
hostName = nebula.HostName(hostNameStr) hostName = nebula.HostName(hostNameStr)
networkOpts = &Opts{ networkOpts = &Opts{
ChildrenOpts: h.mkChildrenOpts(t, runtimeDir), ChildrenOpts: h.mkChildrenOpts(t, runtimeDir),
GarageAdminToken: "admin_token",
} }
) )
@ -341,6 +339,7 @@ func (h *integrationHarness) joinNetwork(
return integrationHarnessNetwork{ return integrationHarnessNetwork{
joinedNetwork, joinedNetwork,
hostName,
network.creationParams, network.creationParams,
networkConfig, networkConfig,
stateDir, stateDir,

View File

@ -273,10 +273,23 @@ func (c *AdminClient) GrantBucketPermissions(
// PeerLayout describes the properties of a garage peer in the context of the // PeerLayout describes the properties of a garage peer in the context of the
// layout of the cluster. // layout of the cluster.
type PeerLayout struct { type PeerLayout struct {
ID string ID string `json:"id"`
Capacity int // Gb (SI units) Capacity int `json:"capacity"` // Gb (SI units)
Zone string Zone string `json:"zone"`
Tags []string Tags []string `json:"tags"`
}
// ClusterLayout describes the layout of the cluster as a whole.
type ClusterLayout struct {
Peers []PeerLayout `json:"roles"`
}
// GetLayout returns the currently applied ClusterLayout.
func (c *AdminClient) GetLayout(ctx context.Context) (ClusterLayout, error) {
// https://garagehq.deuxfleurs.fr/api/garage-admin-v1.html#tag/Layout/operation/GetLayout
var res ClusterLayout
err := c.do(ctx, &res, "GET", "/v1/layout", nil)
return res, err
} }
// ApplyLayout modifies the layout of the garage cluster. Only layout of the // ApplyLayout modifies the layout of the garage cluster. Only layout of the
@ -284,21 +297,9 @@ type PeerLayout struct {
func (c *AdminClient) ApplyLayout( func (c *AdminClient) ApplyLayout(
ctx context.Context, peers []PeerLayout, ctx context.Context, peers []PeerLayout,
) error { ) error {
type peerLayout struct {
ID string `json:"id"`
Capacity int `json:"capacity"`
Zone string `json:"zone"`
Tags []string `json:"tags"`
}
{ {
// https://garagehq.deuxfleurs.fr/api/garage-admin-v1.html#tag/Layout/operation/ApplyLayout // https://garagehq.deuxfleurs.fr/api/garage-admin-v1.html#tag/Layout/operation/ApplyLayout
clusterLayout := make([]peerLayout, len(peers)) err := c.do(ctx, nil, "POST", "/v1/layout", peers)
for i := range peers {
clusterLayout[i] = peerLayout(peers[i])
}
err := c.do(ctx, nil, "POST", "/v1/layout", clusterLayout)
if err != nil { if err != nil {
return fmt.Errorf("staging layout changes: %w", err) return fmt.Errorf("staging layout changes: %w", err)
} }
@ -307,7 +308,7 @@ func (c *AdminClient) ApplyLayout(
// https://garagehq.deuxfleurs.fr/api/garage-admin-v1.html#tag/Layout/operation/GetLayout // https://garagehq.deuxfleurs.fr/api/garage-admin-v1.html#tag/Layout/operation/GetLayout
var clusterLayout struct { var clusterLayout struct {
Version int `json:"version"` Version int `json:"version"`
StagedRoleChanges []peerLayout `json:"stagedRoleChanges"` StagedRoleChanges []PeerLayout `json:"stagedRoleChanges"`
} }
if err := c.do(ctx, &clusterLayout, "GET", "/v1/layout", nil); err != nil { if err := c.do(ctx, &clusterLayout, "GET", "/v1/layout", nil); err != nil {

View File

@ -10,12 +10,14 @@ require (
github.com/minio/minio-go/v7 v7.0.28 github.com/minio/minio-go/v7 v7.0.28
github.com/slackhq/nebula v1.6.1 github.com/slackhq/nebula v1.6.1
github.com/spf13/pflag v1.0.5 github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.9.0
github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
) )
require ( require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dustin/go-humanize v1.0.0 // indirect github.com/dustin/go-humanize v1.0.0 // indirect
github.com/google/uuid v1.1.1 // indirect github.com/google/uuid v1.1.1 // indirect
github.com/gopherjs/gopherjs v1.17.2 // indirect github.com/gopherjs/gopherjs v1.17.2 // indirect
@ -28,6 +30,7 @@ require (
github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rs/xid v1.2.1 // indirect github.com/rs/xid v1.2.1 // indirect
github.com/sirupsen/logrus v1.8.1 // indirect github.com/sirupsen/logrus v1.8.1 // indirect
github.com/smartystreets/assertions v1.13.0 // indirect github.com/smartystreets/assertions v1.13.0 // indirect

View File

@ -1,9 +1,5 @@
code.betamike.com/micropelago/pmux v0.0.0-20240719134913-f5fce902e8c4 h1:n4pGP12kgdH5kCTF4TZYpOjWuAR/zZLpM9j3neeVMEk= code.betamike.com/micropelago/pmux v0.0.0-20240719134913-f5fce902e8c4 h1:n4pGP12kgdH5kCTF4TZYpOjWuAR/zZLpM9j3neeVMEk=
code.betamike.com/micropelago/pmux v0.0.0-20240719134913-f5fce902e8c4/go.mod h1:WlEWacLREVfIQl1IlBjKzuDgL56DFRvyl7YiL5gGP4w= code.betamike.com/micropelago/pmux v0.0.0-20240719134913-f5fce902e8c4/go.mod h1:WlEWacLREVfIQl1IlBjKzuDgL56DFRvyl7YiL5gGP4w=
dev.mediocregopher.com/mediocre-go-lib.git v0.0.0-20240511135822-4ab1176672d7 h1:wKQ3bXzG+KQDtRAN/xaRZ4aQtJe1pccleG6V43MvFxw=
dev.mediocregopher.com/mediocre-go-lib.git v0.0.0-20240511135822-4ab1176672d7/go.mod h1:nP+AtQWrc3k5qq5y3ABiBLkOfUPlk/FO9fpTFpF+jgs=
dev.mediocregopher.com/mediocre-go-lib.git v0.0.0-20241023133804-cedd67cf2652 h1:onyZbHYR1GJJOVd8iRtwVpgrowukWNl3EA0gWYRxixs=
dev.mediocregopher.com/mediocre-go-lib.git v0.0.0-20241023133804-cedd67cf2652/go.mod h1:nP+AtQWrc3k5qq5y3ABiBLkOfUPlk/FO9fpTFpF+jgs=
dev.mediocregopher.com/mediocre-go-lib.git v0.0.0-20241023182613-55984cdf5233 h1:Ea4HixNfDNDPh7zMngPpEeDf8gpociSPEROBFBedqIY= dev.mediocregopher.com/mediocre-go-lib.git v0.0.0-20241023182613-55984cdf5233 h1:Ea4HixNfDNDPh7zMngPpEeDf8gpociSPEROBFBedqIY=
dev.mediocregopher.com/mediocre-go-lib.git v0.0.0-20241023182613-55984cdf5233/go.mod h1:nP+AtQWrc3k5qq5y3ABiBLkOfUPlk/FO9fpTFpF+jgs= dev.mediocregopher.com/mediocre-go-lib.git v0.0.0-20241023182613-55984cdf5233/go.mod h1:nP+AtQWrc3k5qq5y3ABiBLkOfUPlk/FO9fpTFpF+jgs=
github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls= github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls=
@ -69,8 +65,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c h1:u6SKchux2yDvFQnDHS3lPnIRmfVJ5Sxy3ao2SIdysLQ= github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c h1:u6SKchux2yDvFQnDHS3lPnIRmfVJ5Sxy3ao2SIdysLQ=
github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c/go.mod h1:hzIxponao9Kjc7aWznkXaL4U4TWaDSs8zcsY4Ka08nM= github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c/go.mod h1:hzIxponao9Kjc7aWznkXaL4U4TWaDSs8zcsY4Ka08nM=
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29 h1:tkVvjkPTB7pnW3jnid7kNyAMPVWllTNOf/qKDze4p9o= golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29 h1:tkVvjkPTB7pnW3jnid7kNyAMPVWllTNOf/qKDze4p9o=

View File

@ -1,13 +1,35 @@
package nebula package nebula
import "github.com/slackhq/nebula/cert" import (
"fmt"
"github.com/slackhq/nebula/cert"
)
// Certificate wraps a NebulaCertificate to provide convenient (and consistent) // Certificate wraps a NebulaCertificate to provide convenient (and consistent)
// text (un)marshaling methods. // text (un)marshaling methods as well as normalization for equality checking.
type Certificate struct { type Certificate struct {
inner cert.NebulaCertificate inner cert.NebulaCertificate
} }
// NewCertificate returns a Certificate wrapping the given one.
func NewCertificate(inner cert.NebulaCertificate) (Certificate, error) {
// normalize the inner cert by marshaling to and unmarshaling from the PEM.
// This allows equality checking in tests to work between certs which have
// never been written to disk and those which have.
b, err := inner.MarshalToPEM()
if err != nil {
return Certificate{}, fmt.Errorf("marshaling to PEM: %w", err)
}
normInner, _, err := cert.UnmarshalNebulaCertificateFromPEM(b)
if err != nil {
return Certificate{}, fmt.Errorf("unmarshaling from PEM: %w", err)
}
return Certificate{inner: *normInner}, nil
}
// Unwrap returns the wrapped NebulaCertificate type. // Unwrap returns the wrapped NebulaCertificate type.
func (c Certificate) Unwrap() *cert.NebulaCertificate { func (c Certificate) Unwrap() *cert.NebulaCertificate {
return &c.inner return &c.inner

View File

@ -90,7 +90,12 @@ func NewHostCert(
return Certificate{}, fmt.Errorf("signing host cert with ca.key: %w", err) return Certificate{}, fmt.Errorf("signing host cert with ca.key: %w", err)
} }
return Certificate{hostCert}, nil c, err := NewCertificate(hostCert)
if err != nil {
return Certificate{}, fmt.Errorf("wrapping cert: %w", err)
}
return c, nil
} }
// NewHostCredentials generates a new key/cert for a nebula host using the CA // NewHostCredentials generates a new key/cert for a nebula host using the CA
@ -136,7 +141,7 @@ func NewCACredentials(domain string, subnet IPNet) (CACredentials, error) {
expireAt = now.Add(2 * 365 * 24 * time.Hour) expireAt = now.Add(2 * 365 * 24 * time.Hour)
) )
caCert := cert.NebulaCertificate{ caCertInner := cert.NebulaCertificate{
Details: cert.NebulaCertificateDetails{ Details: cert.NebulaCertificateDetails{
Name: fmt.Sprintf("%s isle root cert", domain), Name: fmt.Sprintf("%s isle root cert", domain),
Subnets: []*net.IPNet{(*net.IPNet)(&subnet)}, Subnets: []*net.IPNet{(*net.IPNet)(&subnet)},
@ -147,13 +152,18 @@ func NewCACredentials(domain string, subnet IPNet) (CACredentials, error) {
}, },
} }
if err := signCert(&caCert, signingPrivKey); err != nil { if err := signCert(&caCertInner, signingPrivKey); err != nil {
return CACredentials{}, fmt.Errorf("signing caCert: %w", err) return CACredentials{}, fmt.Errorf("signing caCert: %w", err)
} }
caCert, err := NewCertificate(caCertInner)
if err != nil {
return CACredentials{}, fmt.Errorf("wrapping caCert: %w", err)
}
return CACredentials{ return CACredentials{
Public: CAPublicCredentials{ Public: CAPublicCredentials{
Cert: Certificate{caCert}, Cert: caCert,
SigningKey: signingPubKey, SigningKey: signingPubKey,
}, },
SigningPrivateKey: signingPrivKey, SigningPrivateKey: signingPrivKey,