2022-09-05 03:06:08 -04:00
|
|
|
/*
|
|
|
|
Copyright (c) Edgeless Systems GmbH
|
|
|
|
|
|
|
|
SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
*/
|
|
|
|
|
2022-06-28 12:33:27 -04:00
|
|
|
package initserver
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"errors"
|
|
|
|
"net"
|
2022-08-01 03:11:13 -04:00
|
|
|
"strings"
|
2022-07-08 04:59:59 -04:00
|
|
|
"sync"
|
2022-06-28 12:33:27 -04:00
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
2022-09-21 07:47:57 -04:00
|
|
|
"github.com/edgelesssys/constellation/v2/bootstrapper/initproto"
|
2023-01-18 10:49:55 -05:00
|
|
|
"github.com/edgelesssys/constellation/v2/internal/atls"
|
2022-09-21 07:47:57 -04:00
|
|
|
"github.com/edgelesssys/constellation/v2/internal/crypto/testvector"
|
|
|
|
"github.com/edgelesssys/constellation/v2/internal/file"
|
2023-01-16 05:19:03 -05:00
|
|
|
kmssetup "github.com/edgelesssys/constellation/v2/internal/kms/setup"
|
2023-03-02 09:08:31 -05:00
|
|
|
"github.com/edgelesssys/constellation/v2/internal/kms/uri"
|
2022-09-21 07:47:57 -04:00
|
|
|
"github.com/edgelesssys/constellation/v2/internal/logger"
|
2023-01-18 10:49:55 -05:00
|
|
|
"github.com/edgelesssys/constellation/v2/internal/oid"
|
2023-01-06 06:04:36 -05:00
|
|
|
"github.com/edgelesssys/constellation/v2/internal/versions/components"
|
2022-06-28 12:33:27 -04:00
|
|
|
"github.com/spf13/afero"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"go.uber.org/goleak"
|
2022-11-26 13:44:34 -05:00
|
|
|
"golang.org/x/crypto/bcrypt"
|
2022-06-28 12:33:27 -04:00
|
|
|
)
|
|
|
|
|
|
|
|
func TestMain(m *testing.M) {
|
2023-01-16 05:19:03 -05:00
|
|
|
goleak.VerifyTestMain(m,
|
|
|
|
// https://github.com/census-instrumentation/opencensus-go/issues/1262
|
|
|
|
goleak.IgnoreTopFunction("go.opencensus.io/stats/view.(*worker).start"),
|
|
|
|
)
|
2022-06-28 12:33:27 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestNew(t *testing.T) {
|
2022-07-05 08:14:11 -04:00
|
|
|
fh := file.NewHandler(afero.NewMemMapFs())
|
2022-11-26 13:44:34 -05:00
|
|
|
|
|
|
|
testCases := map[string]struct {
|
|
|
|
metadata stubMetadata
|
|
|
|
wantErr bool
|
|
|
|
}{
|
|
|
|
"success": {
|
|
|
|
metadata: stubMetadata{initSecretHashVal: []byte("hash")},
|
|
|
|
},
|
|
|
|
"empty init secret hash": {
|
|
|
|
metadata: stubMetadata{initSecretHashVal: nil},
|
|
|
|
wantErr: true,
|
|
|
|
},
|
|
|
|
"metadata error": {
|
|
|
|
metadata: stubMetadata{initSecretHashErr: errors.New("error")},
|
|
|
|
wantErr: true,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for name, tc := range testCases {
|
|
|
|
t.Run(name, func(t *testing.T) {
|
|
|
|
assert := assert.New(t)
|
|
|
|
|
2023-01-18 10:49:55 -05:00
|
|
|
server, err := New(context.TODO(), newFakeLock(), &stubClusterInitializer{}, atls.NewFakeIssuer(oid.Dummy{}), fh, &tc.metadata, logger.NewTest(t))
|
2022-11-26 13:44:34 -05:00
|
|
|
if tc.wantErr {
|
|
|
|
assert.Error(err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
assert.NoError(err)
|
|
|
|
assert.NotNil(server)
|
|
|
|
assert.NotEmpty(server.initSecretHash)
|
|
|
|
assert.NotNil(server.log)
|
|
|
|
assert.NotNil(server.nodeLock)
|
|
|
|
assert.NotNil(server.initializer)
|
|
|
|
assert.NotNil(server.grpcServer)
|
|
|
|
assert.NotNil(server.fileHandler)
|
|
|
|
assert.NotNil(server.disk)
|
|
|
|
})
|
|
|
|
}
|
2022-06-28 12:33:27 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestInit(t *testing.T) {
|
|
|
|
someErr := errors.New("failed")
|
2022-07-08 04:59:59 -04:00
|
|
|
lockedLock := newFakeLock()
|
2022-07-26 04:58:39 -04:00
|
|
|
aqcuiredLock, lockErr := lockedLock.TryLockOnce(nil)
|
2022-07-08 04:59:59 -04:00
|
|
|
require.True(t, aqcuiredLock)
|
|
|
|
require.Nil(t, lockErr)
|
2022-06-28 12:33:27 -04:00
|
|
|
|
2022-11-26 13:44:34 -05:00
|
|
|
initSecret := []byte("password")
|
|
|
|
initSecretHash, err := bcrypt.GenerateFromPassword(initSecret, bcrypt.DefaultCost)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
2023-03-02 09:08:31 -05:00
|
|
|
masterSecret := uri.MasterSecret{Key: []byte("secret"), Salt: []byte("salt")}
|
2023-01-16 05:19:03 -05:00
|
|
|
|
2022-06-28 12:33:27 -04:00
|
|
|
testCases := map[string]struct {
|
2022-11-26 13:44:34 -05:00
|
|
|
nodeLock *fakeLock
|
|
|
|
initializer ClusterInitializer
|
|
|
|
disk encryptedDisk
|
|
|
|
fileHandler file.Handler
|
|
|
|
req *initproto.InitRequest
|
|
|
|
initSecretHash []byte
|
|
|
|
wantErr bool
|
|
|
|
wantShutdown bool
|
2022-06-28 12:33:27 -04:00
|
|
|
}{
|
|
|
|
"successful init": {
|
2022-11-26 13:44:34 -05:00
|
|
|
nodeLock: newFakeLock(),
|
|
|
|
initializer: &stubClusterInitializer{},
|
|
|
|
disk: &stubDisk{},
|
|
|
|
fileHandler: file.NewHandler(afero.NewMemMapFs()),
|
|
|
|
initSecretHash: initSecretHash,
|
2023-03-02 09:08:31 -05:00
|
|
|
req: &initproto.InitRequest{InitSecret: initSecret, KmsUri: masterSecret.EncodeToURI(), StorageUri: uri.NoStoreURI},
|
2022-06-28 12:33:27 -04:00
|
|
|
},
|
|
|
|
"node locked": {
|
2022-11-26 13:44:34 -05:00
|
|
|
nodeLock: lockedLock,
|
|
|
|
initializer: &stubClusterInitializer{},
|
|
|
|
disk: &stubDisk{},
|
|
|
|
fileHandler: file.NewHandler(afero.NewMemMapFs()),
|
2023-03-02 09:08:31 -05:00
|
|
|
req: &initproto.InitRequest{InitSecret: initSecret, KmsUri: masterSecret.EncodeToURI(), StorageUri: uri.NoStoreURI},
|
2022-11-26 13:44:34 -05:00
|
|
|
initSecretHash: initSecretHash,
|
|
|
|
wantErr: true,
|
|
|
|
wantShutdown: true,
|
2022-06-28 12:33:27 -04:00
|
|
|
},
|
|
|
|
"disk open error": {
|
2022-11-26 13:44:34 -05:00
|
|
|
nodeLock: newFakeLock(),
|
|
|
|
initializer: &stubClusterInitializer{},
|
|
|
|
disk: &stubDisk{openErr: someErr},
|
|
|
|
fileHandler: file.NewHandler(afero.NewMemMapFs()),
|
2023-03-02 09:08:31 -05:00
|
|
|
req: &initproto.InitRequest{InitSecret: initSecret, KmsUri: masterSecret.EncodeToURI(), StorageUri: uri.NoStoreURI},
|
2022-11-26 13:44:34 -05:00
|
|
|
initSecretHash: initSecretHash,
|
|
|
|
wantErr: true,
|
2022-06-28 12:33:27 -04:00
|
|
|
},
|
|
|
|
"disk uuid error": {
|
2022-11-26 13:44:34 -05:00
|
|
|
nodeLock: newFakeLock(),
|
|
|
|
initializer: &stubClusterInitializer{},
|
|
|
|
disk: &stubDisk{uuidErr: someErr},
|
|
|
|
fileHandler: file.NewHandler(afero.NewMemMapFs()),
|
2023-03-02 09:08:31 -05:00
|
|
|
req: &initproto.InitRequest{InitSecret: initSecret, KmsUri: masterSecret.EncodeToURI(), StorageUri: uri.NoStoreURI},
|
2022-11-26 13:44:34 -05:00
|
|
|
initSecretHash: initSecretHash,
|
|
|
|
wantErr: true,
|
2022-06-28 12:33:27 -04:00
|
|
|
},
|
|
|
|
"disk update passphrase error": {
|
2022-11-26 13:44:34 -05:00
|
|
|
nodeLock: newFakeLock(),
|
|
|
|
initializer: &stubClusterInitializer{},
|
|
|
|
disk: &stubDisk{updatePassphraseErr: someErr},
|
|
|
|
fileHandler: file.NewHandler(afero.NewMemMapFs()),
|
2023-03-02 09:08:31 -05:00
|
|
|
req: &initproto.InitRequest{InitSecret: initSecret, KmsUri: masterSecret.EncodeToURI(), StorageUri: uri.NoStoreURI},
|
2022-11-26 13:44:34 -05:00
|
|
|
initSecretHash: initSecretHash,
|
|
|
|
wantErr: true,
|
2022-06-28 12:33:27 -04:00
|
|
|
},
|
|
|
|
"write state file error": {
|
2022-11-26 13:44:34 -05:00
|
|
|
nodeLock: newFakeLock(),
|
|
|
|
initializer: &stubClusterInitializer{},
|
|
|
|
disk: &stubDisk{},
|
|
|
|
fileHandler: file.NewHandler(afero.NewReadOnlyFs(afero.NewMemMapFs())),
|
2023-03-02 09:08:31 -05:00
|
|
|
req: &initproto.InitRequest{InitSecret: initSecret, KmsUri: masterSecret.EncodeToURI(), StorageUri: uri.NoStoreURI},
|
2022-11-26 13:44:34 -05:00
|
|
|
initSecretHash: initSecretHash,
|
|
|
|
wantErr: true,
|
2022-06-28 12:33:27 -04:00
|
|
|
},
|
|
|
|
"initialize cluster error": {
|
2022-11-26 13:44:34 -05:00
|
|
|
nodeLock: newFakeLock(),
|
|
|
|
initializer: &stubClusterInitializer{initClusterErr: someErr},
|
|
|
|
disk: &stubDisk{},
|
|
|
|
fileHandler: file.NewHandler(afero.NewMemMapFs()),
|
2023-03-02 09:08:31 -05:00
|
|
|
req: &initproto.InitRequest{InitSecret: initSecret, KmsUri: masterSecret.EncodeToURI(), StorageUri: uri.NoStoreURI},
|
2022-11-26 13:44:34 -05:00
|
|
|
initSecretHash: initSecretHash,
|
|
|
|
wantErr: true,
|
|
|
|
},
|
|
|
|
"wrong initSecret": {
|
|
|
|
nodeLock: newFakeLock(),
|
|
|
|
initializer: &stubClusterInitializer{},
|
|
|
|
disk: &stubDisk{},
|
|
|
|
fileHandler: file.NewHandler(afero.NewMemMapFs()),
|
|
|
|
initSecretHash: initSecretHash,
|
|
|
|
req: &initproto.InitRequest{InitSecret: []byte("wrongpassword")},
|
|
|
|
wantErr: true,
|
2022-06-28 12:33:27 -04:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for name, tc := range testCases {
|
|
|
|
t.Run(name, func(t *testing.T) {
|
|
|
|
assert := assert.New(t)
|
|
|
|
|
|
|
|
serveStopper := newStubServeStopper()
|
|
|
|
server := &Server{
|
2022-11-26 13:44:34 -05:00
|
|
|
nodeLock: tc.nodeLock,
|
|
|
|
initializer: tc.initializer,
|
|
|
|
disk: tc.disk,
|
|
|
|
fileHandler: tc.fileHandler,
|
|
|
|
log: logger.NewTest(t),
|
|
|
|
grpcServer: serveStopper,
|
|
|
|
cleaner: &fakeCleaner{serveStopper: serveStopper},
|
|
|
|
initSecretHash: tc.initSecretHash,
|
2022-06-28 12:33:27 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
kubeconfig, err := server.Init(context.Background(), tc.req)
|
|
|
|
|
|
|
|
if tc.wantErr {
|
|
|
|
assert.Error(err)
|
|
|
|
|
|
|
|
if tc.wantShutdown {
|
|
|
|
select {
|
|
|
|
case <-serveStopper.shutdownCalled:
|
|
|
|
case <-time.After(time.Second):
|
|
|
|
t.Fatal("grpc server did not shut down")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
assert.NoError(err)
|
|
|
|
assert.NotNil(kubeconfig)
|
2022-07-26 04:58:39 -04:00
|
|
|
assert.False(server.nodeLock.TryLockOnce(nil)) // lock should be locked
|
2022-06-28 12:33:27 -04:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-01 03:11:13 -04:00
|
|
|
func TestSetupDisk(t *testing.T) {
|
|
|
|
testCases := map[string]struct {
|
2023-01-16 05:19:03 -05:00
|
|
|
uuid string
|
|
|
|
masterKey []byte
|
|
|
|
salt []byte
|
|
|
|
wantKey []byte
|
2022-08-01 03:11:13 -04:00
|
|
|
}{
|
|
|
|
"lower case uuid": {
|
2023-01-16 05:19:03 -05:00
|
|
|
uuid: strings.ToLower(testvector.HKDF0xFF.Info),
|
|
|
|
masterKey: testvector.HKDF0xFF.Secret,
|
|
|
|
salt: testvector.HKDF0xFF.Salt,
|
|
|
|
wantKey: testvector.HKDF0xFF.Output,
|
2022-08-01 03:11:13 -04:00
|
|
|
},
|
|
|
|
"upper case uuid": {
|
2023-01-16 05:19:03 -05:00
|
|
|
uuid: strings.ToUpper(testvector.HKDF0xFF.Info),
|
|
|
|
masterKey: testvector.HKDF0xFF.Secret,
|
|
|
|
salt: testvector.HKDF0xFF.Salt,
|
|
|
|
wantKey: testvector.HKDF0xFF.Output,
|
2022-08-01 03:11:13 -04:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for name, tc := range testCases {
|
|
|
|
t.Run(name, func(t *testing.T) {
|
|
|
|
assert := assert.New(t)
|
2023-01-16 05:19:03 -05:00
|
|
|
require := require.New(t)
|
2022-08-01 03:11:13 -04:00
|
|
|
|
|
|
|
disk := &fakeDisk{
|
|
|
|
uuid: tc.uuid,
|
|
|
|
wantKey: tc.wantKey,
|
|
|
|
}
|
|
|
|
server := &Server{
|
|
|
|
disk: disk,
|
|
|
|
}
|
|
|
|
|
2023-03-02 09:08:31 -05:00
|
|
|
masterSecret := uri.MasterSecret{Key: tc.masterKey, Salt: tc.salt}
|
2023-01-16 05:19:03 -05:00
|
|
|
|
2023-03-02 09:08:31 -05:00
|
|
|
cloudKms, err := kmssetup.KMS(context.Background(), uri.NoStoreURI, masterSecret.EncodeToURI())
|
2023-01-16 05:19:03 -05:00
|
|
|
require.NoError(err)
|
|
|
|
assert.NoError(server.setupDisk(context.Background(), cloudKms))
|
2022-08-01 03:11:13 -04:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type fakeDisk struct {
|
|
|
|
uuid string
|
|
|
|
wantKey []byte
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *fakeDisk) Open() error {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *fakeDisk) Close() error {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *fakeDisk) UUID() (string, error) {
|
|
|
|
return d.uuid, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *fakeDisk) UpdatePassphrase(passphrase string) error {
|
|
|
|
if passphrase != string(d.wantKey) {
|
|
|
|
return errors.New("wrong passphrase")
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-06-28 12:33:27 -04:00
|
|
|
type stubDisk struct {
|
|
|
|
openErr error
|
|
|
|
closeErr error
|
|
|
|
uuid string
|
|
|
|
uuidErr error
|
|
|
|
updatePassphraseErr error
|
|
|
|
updatePassphraseCalled bool
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *stubDisk) Open() error {
|
|
|
|
return d.openErr
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *stubDisk) Close() error {
|
|
|
|
return d.closeErr
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *stubDisk) UUID() (string, error) {
|
|
|
|
return d.uuid, d.uuidErr
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *stubDisk) UpdatePassphrase(string) error {
|
|
|
|
d.updatePassphraseCalled = true
|
|
|
|
return d.updatePassphraseErr
|
|
|
|
}
|
|
|
|
|
|
|
|
type stubClusterInitializer struct {
|
|
|
|
initClusterKubeconfig []byte
|
|
|
|
initClusterErr error
|
|
|
|
}
|
|
|
|
|
2022-08-12 09:59:45 -04:00
|
|
|
func (i *stubClusterInitializer) InitCluster(
|
2023-02-10 07:27:22 -05:00
|
|
|
context.Context, string, string, string, []byte, []uint32, bool, bool,
|
2023-01-06 06:04:36 -05:00
|
|
|
[]byte, bool, components.Components, *logger.Logger,
|
2022-06-28 12:33:27 -04:00
|
|
|
) ([]byte, error) {
|
|
|
|
return i.initClusterKubeconfig, i.initClusterErr
|
|
|
|
}
|
|
|
|
|
|
|
|
type stubServeStopper struct {
|
|
|
|
shutdownCalled chan struct{}
|
|
|
|
}
|
|
|
|
|
|
|
|
func newStubServeStopper() *stubServeStopper {
|
|
|
|
return &stubServeStopper{shutdownCalled: make(chan struct{}, 1)}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *stubServeStopper) Serve(net.Listener) error {
|
|
|
|
panic("should not be called in a test")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *stubServeStopper) GracefulStop() {
|
|
|
|
s.shutdownCalled <- struct{}{}
|
|
|
|
}
|
2022-07-08 04:59:59 -04:00
|
|
|
|
|
|
|
type fakeLock struct {
|
|
|
|
state *sync.Mutex
|
|
|
|
}
|
|
|
|
|
|
|
|
func newFakeLock() *fakeLock {
|
|
|
|
return &fakeLock{
|
|
|
|
state: &sync.Mutex{},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-26 04:58:39 -04:00
|
|
|
func (l *fakeLock) TryLockOnce(_ []byte) (bool, error) {
|
2022-07-08 04:59:59 -04:00
|
|
|
return l.state.TryLock(), nil
|
|
|
|
}
|
2022-07-14 09:45:04 -04:00
|
|
|
|
|
|
|
type fakeCleaner struct {
|
|
|
|
serveStopper
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *fakeCleaner) Clean() {
|
|
|
|
go f.serveStopper.GracefulStop() // this is not the correct way to do this, but it's fine for testing
|
|
|
|
}
|
2022-11-26 13:44:34 -05:00
|
|
|
|
|
|
|
type stubMetadata struct {
|
|
|
|
initSecretHashVal []byte
|
|
|
|
initSecretHashErr error
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *stubMetadata) InitSecretHash(context.Context) ([]byte, error) {
|
|
|
|
return m.initSecretHashVal, m.initSecretHashErr
|
|
|
|
}
|