diff --git a/coordinator/cmd/coordinator/main.go b/coordinator/cmd/coordinator/main.go index 64040faaa..a6f4524a6 100644 --- a/coordinator/cmd/coordinator/main.go +++ b/coordinator/cmd/coordinator/main.go @@ -17,6 +17,7 @@ import ( gcpcloud "github.com/edgelesssys/constellation/coordinator/cloudprovider/gcp" "github.com/edgelesssys/constellation/coordinator/config" "github.com/edgelesssys/constellation/coordinator/core" + "github.com/edgelesssys/constellation/coordinator/diskencryption" "github.com/edgelesssys/constellation/coordinator/kubernetes" "github.com/edgelesssys/constellation/coordinator/kubernetes/k8sapi" "github.com/edgelesssys/constellation/coordinator/kubernetes/k8sapi/kubectl" @@ -41,6 +42,7 @@ func main() { var cloudControllerManager core.CloudControllerManager var cloudNodeManager core.CloudNodeManager var autoscaler core.ClusterAutoscaler + var encryptedDisk core.EncryptedDisk cfg := zap.NewDevelopmentConfig() logLevelUser := flag.Bool("debug", false, "enables gRPC debug output") @@ -99,6 +101,7 @@ func main() { cloudControllerManager = &gcpcloud.CloudControllerManager{} cloudNodeManager = &gcpcloud.CloudNodeManager{} autoscaler = &gcpcloud.Autoscaler{} + encryptedDisk = diskencryption.New() bindIP = defaultIP bindPort = defaultPort etcdEndpoint = defaultEtcdEndpoint @@ -122,6 +125,7 @@ func main() { cloudControllerManager = &azurecloud.CloudControllerManager{} cloudNodeManager = &azurecloud.CloudNodeManager{} autoscaler = &azurecloud.Autoscaler{} + encryptedDisk = diskencryption.New() bindIP = defaultIP bindPort = defaultPort etcdEndpoint = defaultEtcdEndpoint @@ -136,6 +140,7 @@ func main() { cloudControllerManager = &core.CloudControllerManagerFake{} cloudNodeManager = &core.CloudNodeManagerFake{} autoscaler = &core.ClusterAutoscalerFake{} + encryptedDisk = &core.EncryptedDiskFake{} bindIP = defaultIP bindPort = defaultPort etcdEndpoint = "etcd-storage:2379" @@ -149,5 +154,5 @@ func main() { fileHandler := file.NewHandler(fs) dialer := &net.Dialer{} run(validator, issuer, wg, openTPM, util.GetIPAddr, dialer, fileHandler, kube, - metadata, cloudControllerManager, cloudNodeManager, autoscaler, etcdEndpoint, enforceEtcdTls, bindIP, bindPort, zapLoggerCore) + metadata, cloudControllerManager, cloudNodeManager, autoscaler, encryptedDisk, etcdEndpoint, enforceEtcdTls, bindIP, bindPort, zapLoggerCore) } diff --git a/coordinator/cmd/coordinator/run.go b/coordinator/cmd/coordinator/run.go index 9e67106ae..882f5057a 100644 --- a/coordinator/cmd/coordinator/run.go +++ b/coordinator/cmd/coordinator/run.go @@ -27,7 +27,7 @@ import ( var version = "0.0.0" func run(validator core.QuoteValidator, issuer core.QuoteIssuer, vpn core.VPN, openTPM vtpm.TPMOpenFunc, getPublicIPAddr func() (string, error), dialer pubapi.Dialer, fileHandler file.Handler, - kube core.Cluster, metadata core.ProviderMetadata, cloudControllerManager core.CloudControllerManager, cloudNodeManager core.CloudNodeManager, clusterAutoscaler core.ClusterAutoscaler, etcdEndpoint string, etcdTLS bool, bindIP, bindPort string, zapLoggerCore *zap.Logger, + kube core.Cluster, metadata core.ProviderMetadata, cloudControllerManager core.CloudControllerManager, cloudNodeManager core.CloudNodeManager, clusterAutoscaler core.ClusterAutoscaler, encryptedDisk core.EncryptedDisk, etcdEndpoint string, etcdTLS bool, bindIP, bindPort string, zapLoggerCore *zap.Logger, ) { defer zapLoggerCore.Sync() zapLoggerCore.Info("starting coordinator", zap.String("version", version)) @@ -42,7 +42,7 @@ func run(validator core.QuoteValidator, issuer core.QuoteIssuer, vpn core.VPN, o ForceTLS: etcdTLS, Logger: zapLoggerCore.WithOptions(zap.IncreaseLevel(zap.WarnLevel)).Named("etcd"), } - core, err := core.NewCore(vpn, kube, metadata, cloudControllerManager, cloudNodeManager, clusterAutoscaler, zapLoggerCore, openTPM, etcdStoreFactory, fileHandler) + core, err := core.NewCore(vpn, kube, metadata, cloudControllerManager, cloudNodeManager, clusterAutoscaler, encryptedDisk, zapLoggerCore, openTPM, etcdStoreFactory, fileHandler) if err != nil { zapLoggerCore.Fatal("failed to create core", zap.Error(err)) } diff --git a/coordinator/coordinator_test.go b/coordinator/coordinator_test.go index 625a39289..3886cc0a4 100644 --- a/coordinator/coordinator_test.go +++ b/coordinator/coordinator_test.go @@ -194,7 +194,7 @@ func TestConcurrent(t *testing.T) { func spawnPeer(require *require.Assertions, logger *zap.Logger, dialer *testdialer.BufconnDialer, netw *network, endpoint string) (*grpc.Server, *pubapi.API, *fakeVPN) { vpn := newVPN(netw, endpoint) - cor, err := core.NewCore(vpn, &core.ClusterFake{}, &core.ProviderMetadataFake{}, &core.CloudControllerManagerFake{}, &core.CloudNodeManagerFake{}, &core.ClusterAutoscalerFake{}, logger, vtpm.OpenSimulatedTPM, fakeStoreFactory{}, file.NewHandler(afero.NewMemMapFs())) + cor, err := core.NewCore(vpn, &core.ClusterFake{}, &core.ProviderMetadataFake{}, &core.CloudControllerManagerFake{}, &core.CloudNodeManagerFake{}, &core.ClusterAutoscalerFake{}, &core.EncryptedDiskFake{}, logger, vtpm.OpenSimulatedTPM, fakeStoreFactory{}, file.NewHandler(afero.NewMemMapFs())) require.NoError(err) require.NoError(cor.AdvanceState(state.AcceptingInit, nil, nil)) diff --git a/coordinator/core/cluster_test.go b/coordinator/core/cluster_test.go index 37c785128..fcb12ce56 100644 --- a/coordinator/core/cluster_test.go +++ b/coordinator/core/cluster_test.go @@ -169,7 +169,7 @@ func TestInitCluster(t *testing.T) { zapLogger, err := zap.NewDevelopment() require.NoError(err) - core, err := NewCore(&stubVPN{}, &tc.cluster, &tc.metadata, &tc.cloudControllerManager, &tc.cloudNodeManager, &tc.clusterAutoscaler, zapLogger, vtpm.OpenSimulatedTPM, nil, file.NewHandler(afero.NewMemMapFs())) + core, err := NewCore(&stubVPN{}, &tc.cluster, &tc.metadata, &tc.cloudControllerManager, &tc.cloudNodeManager, &tc.clusterAutoscaler, nil, zapLogger, vtpm.OpenSimulatedTPM, nil, file.NewHandler(afero.NewMemMapFs())) require.NoError(err) kubeconfig, err := core.InitCluster(tc.autoscalingNodeGroups, "cloud-service-account-uri") @@ -284,7 +284,7 @@ func TestJoinCluster(t *testing.T) { zapLogger, err := zap.NewDevelopment() require.NoError(err) - core, err := NewCore(&tc.vpn, &tc.cluster, &tc.metadata, &tc.cloudControllerManager, &tc.cloudNodeManager, &tc.clusterAutoscaler, zapLogger, vtpm.OpenSimulatedTPM, nil, file.NewHandler(afero.NewMemMapFs())) + core, err := NewCore(&tc.vpn, &tc.cluster, &tc.metadata, &tc.cloudControllerManager, &tc.cloudNodeManager, &tc.clusterAutoscaler, nil, zapLogger, vtpm.OpenSimulatedTPM, nil, file.NewHandler(afero.NewMemMapFs())) require.NoError(err) joinReq := kubeadm.BootstrapTokenDiscovery{ diff --git a/coordinator/core/core.go b/coordinator/core/core.go index 99b7fa7f9..64986fbeb 100644 --- a/coordinator/core/core.go +++ b/coordinator/core/core.go @@ -36,6 +36,7 @@ type Core struct { cloudControllerManager CloudControllerManager cloudNodeManager CloudNodeManager clusterAutoscaler ClusterAutoscaler + encryptedDisk EncryptedDisk kms kms.CloudKMS zaplogger *zap.Logger persistentStoreFactory PersistentStoreFactory @@ -46,7 +47,7 @@ type Core struct { // NewCore creates and initializes a new Core object. func NewCore(vpn VPN, kube Cluster, metadata ProviderMetadata, cloudControllerManager CloudControllerManager, cloudNodeManager CloudNodeManager, clusterAutoscaler ClusterAutoscaler, - zapLogger *zap.Logger, openTPM vtpm.TPMOpenFunc, persistentStoreFactory PersistentStoreFactory, fileHandler file.Handler, + encryptedDisk EncryptedDisk, zapLogger *zap.Logger, openTPM vtpm.TPMOpenFunc, persistentStoreFactory PersistentStoreFactory, fileHandler file.Handler, ) (*Core, error) { stor := store.NewStdStore() c := &Core{ @@ -58,6 +59,7 @@ func NewCore(vpn VPN, kube Cluster, cloudNodeManager: cloudNodeManager, cloudControllerManager: cloudControllerManager, clusterAutoscaler: clusterAutoscaler, + encryptedDisk: encryptedDisk, zaplogger: zapLogger, kms: nil, // KMS is set up during init phase persistentStoreFactory: persistentStoreFactory, diff --git a/coordinator/core/core_test.go b/coordinator/core/core_test.go index b71bbf2ee..4856b9d57 100644 --- a/coordinator/core/core_test.go +++ b/coordinator/core/core_test.go @@ -33,7 +33,7 @@ func TestGetNextNodeIP(t *testing.T) { assert := assert.New(t) require := require.New(t) - core, err := NewCore(&stubVPN{}, nil, nil, nil, nil, nil, zaptest.NewLogger(t), nil, nil, file.NewHandler(afero.NewMemMapFs())) + core, err := NewCore(&stubVPN{}, nil, nil, nil, nil, nil, nil, zaptest.NewLogger(t), nil, nil, file.NewHandler(afero.NewMemMapFs())) require.NoError(err) require.NoError(core.InitializeStoreIPs()) @@ -76,7 +76,7 @@ func TestSwitchToPersistentStore(t *testing.T) { require := require.New(t) storeFactory := &fakeStoreFactory{} - core, err := NewCore(&stubVPN{}, nil, nil, nil, nil, nil, zaptest.NewLogger(t), nil, storeFactory, file.NewHandler(afero.NewMemMapFs())) + core, err := NewCore(&stubVPN{}, nil, nil, nil, nil, nil, nil, zaptest.NewLogger(t), nil, storeFactory, file.NewHandler(afero.NewMemMapFs())) require.NoError(err) require.NoError(core.SwitchToPersistentStore()) @@ -90,7 +90,7 @@ func TestGetIDs(t *testing.T) { assert := assert.New(t) require := require.New(t) - core, err := NewCore(&stubVPN{}, nil, nil, nil, nil, nil, zaptest.NewLogger(t), nil, nil, file.NewHandler(afero.NewMemMapFs())) + core, err := NewCore(&stubVPN{}, nil, nil, nil, nil, nil, nil, zaptest.NewLogger(t), nil, nil, file.NewHandler(afero.NewMemMapFs())) require.NoError(err) _, _, err = core.GetIDs(nil) @@ -114,7 +114,7 @@ func TestNotifyNodeHeartbeat(t *testing.T) { assert := assert.New(t) require := require.New(t) - core, err := NewCore(&stubVPN{}, nil, nil, nil, nil, nil, zaptest.NewLogger(t), nil, nil, file.NewHandler(afero.NewMemMapFs())) + core, err := NewCore(&stubVPN{}, nil, nil, nil, nil, nil, nil, zaptest.NewLogger(t), nil, nil, file.NewHandler(afero.NewMemMapFs())) require.NoError(err) const ip = "192.0.2.1" @@ -127,7 +127,7 @@ func TestDeriveKey(t *testing.T) { assert := assert.New(t) require := require.New(t) - core, err := NewCore(&stubVPN{}, nil, nil, nil, nil, nil, zaptest.NewLogger(t), nil, nil, file.NewHandler(afero.NewMemMapFs())) + core, err := NewCore(&stubVPN{}, nil, nil, nil, nil, nil, nil, zaptest.NewLogger(t), nil, nil, file.NewHandler(afero.NewMemMapFs())) require.NoError(err) // error when no kms is set up @@ -208,7 +208,7 @@ func TestInitialize(t *testing.T) { }).ToFile(fileHandler)) } - core, err := NewCore(&stubVPN{}, nil, nil, nil, nil, nil, zaptest.NewLogger(t), openTPM, nil, fileHandler) + core, err := NewCore(&stubVPN{}, nil, nil, nil, nil, nil, nil, zaptest.NewLogger(t), openTPM, nil, fileHandler) require.NoError(err) if tc.expectPanic { @@ -266,7 +266,7 @@ func TestPersistNodeState(t *testing.T) { require.NoError(err) require.NoError(file.Close()) } - core, err := NewCore(tc.vpn, nil, nil, nil, nil, nil, zaptest.NewLogger(t), nil, nil, fileHandler) + core, err := NewCore(tc.vpn, nil, nil, nil, nil, nil, nil, zaptest.NewLogger(t), nil, nil, fileHandler) require.NoError(err) err = core.PersistNodeState(role.Coordinator, []byte("owner-id"), []byte("cluster-id")) if tc.errExpected { diff --git a/coordinator/core/diskencryption.go b/coordinator/core/diskencryption.go new file mode 100644 index 000000000..a16d808ae --- /dev/null +++ b/coordinator/core/diskencryption.go @@ -0,0 +1,57 @@ +package core + +import ( + "fmt" +) + +// GetDiskUUID gets the disk's UUID. +func (c *Core) GetDiskUUID() (string, error) { + if err := c.encryptedDisk.Open(); err != nil { + return "", fmt.Errorf("retrieving uuid of encrypted disk failed: cannot open disk: %w", err) + } + defer c.encryptedDisk.Close() + uuid, err := c.encryptedDisk.UUID() + if err != nil { + return "", fmt.Errorf("cannot retrieve uuid of disk: %w", err) + } + return uuid, nil +} + +// UpdateDiskPassphrase switches the initial random passphrase of the encrypted disk to a permanent passphrase. +func (c *Core) UpdateDiskPassphrase(passphrase string) error { + if err := c.encryptedDisk.Open(); err != nil { + return fmt.Errorf("updating passphrase of encrypted disk failed: cannot open disk: %w", err) + } + defer c.encryptedDisk.Close() + return c.encryptedDisk.UpdatePassphrase(passphrase) +} + +// EncryptedDisk manages the encrypted state disk. +type EncryptedDisk interface { + // Open prepares the underlying device for disk operations. + Open() error + // Close closes the underlying device. + Close() error + // UUID gets the device's UUID. + UUID() (string, error) + // UpdatePassphrase switches the initial random passphrase of the encrypted disk to a permanent passphrase. + UpdatePassphrase(passphrase string) error +} + +type EncryptedDiskFake struct{} + +func (f *EncryptedDiskFake) UUID() (string, error) { + return "fake-disk-uuid", nil +} + +func (f *EncryptedDiskFake) UpdatePassphrase(passphrase string) error { + return nil +} + +func (f *EncryptedDiskFake) Open() error { + return nil +} + +func (f *EncryptedDiskFake) Close() error { + return nil +} diff --git a/coordinator/core/diskencryption_test.go b/coordinator/core/diskencryption_test.go new file mode 100644 index 000000000..b3c317785 --- /dev/null +++ b/coordinator/core/diskencryption_test.go @@ -0,0 +1,122 @@ +package core + +import ( + "errors" + "testing" + + "github.com/edgelesssys/constellation/cli/file" + "github.com/spf13/afero" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" +) + +func TestGetDiskUUID(t *testing.T) { + testCases := map[string]struct { + expectedUUID string + openErr error + uuidErr error + errExpected bool + }{ + "getting uuid works": { + expectedUUID: "uuid", + }, + "open can fail": { + openErr: errors.New("open-error"), + errExpected: true, + }, + "getting disk uuid can fail": { + uuidErr: errors.New("uuid-err"), + errExpected: true, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + zapLogger, err := zap.NewDevelopment() + require.NoError(err) + diskStub := encryptedDiskStub{ + openErr: tc.openErr, + uuidErr: tc.uuidErr, + uuid: tc.expectedUUID, + } + core, err := NewCore(&stubVPN{}, nil, nil, nil, nil, nil, &diskStub, zapLogger, nil, nil, file.NewHandler(afero.NewMemMapFs())) + require.NoError(err) + uuid, err := core.GetDiskUUID() + if tc.errExpected { + assert.Error(err) + return + } + require.NoError(err) + + assert.Equal(tc.expectedUUID, uuid) + }) + } +} + +func TestUpdateDiskPassphrase(t *testing.T) { + testCases := map[string]struct { + openErr error + updatePassphraseErr error + errExpected bool + }{ + "updating passphrase works": {}, + "open can fail": { + openErr: errors.New("open-error"), + errExpected: true, + }, + "updating disk passphrase can fail": { + updatePassphraseErr: errors.New("update-err"), + errExpected: true, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + zapLogger, err := zap.NewDevelopment() + require.NoError(err) + diskStub := encryptedDiskStub{ + openErr: tc.openErr, + updatePassphraseErr: tc.updatePassphraseErr, + } + core, err := NewCore(&stubVPN{}, nil, nil, nil, nil, nil, &diskStub, zapLogger, nil, nil, file.NewHandler(afero.NewMemMapFs())) + require.NoError(err) + err = core.UpdateDiskPassphrase("passphrase") + if tc.errExpected { + assert.Error(err) + return + } + require.NoError(err) + }) + } +} + +type encryptedDiskStub struct { + openErr error + closeErr error + uuid string + uuidErr error + updatePassphraseErr error +} + +func (s *encryptedDiskStub) UUID() (string, error) { + return s.uuid, s.uuidErr +} + +func (s *encryptedDiskStub) UpdatePassphrase(passphrase string) error { + return s.updatePassphraseErr +} + +func (s *encryptedDiskStub) Open() error { + return s.openErr +} + +func (s *encryptedDiskStub) Close() error { + return s.closeErr +} diff --git a/coordinator/core/legacy_test.go b/coordinator/core/legacy_test.go index 5f2482240..40b5fac97 100644 --- a/coordinator/core/legacy_test.go +++ b/coordinator/core/legacy_test.go @@ -145,11 +145,12 @@ func newMockCoreWithDialer(dialer *bufconnDialer) (*Core, *pubapi.API, error) { ccmFake := &CloudControllerManagerFake{} cnmFake := &CloudNodeManagerFake{} autoscalerFake := &ClusterAutoscalerFake{} + encryptedDiskFake := &EncryptedDiskFake{} getPublicAddr := func() (string, error) { return "192.0.2.1", nil } - core, err := NewCore(vpn, kubeFake, metadataFake, ccmFake, cnmFake, autoscalerFake, zapLogger, vtpm.OpenSimulatedTPM, &fakeStoreFactory{}, file.NewHandler(afero.NewMemMapFs())) + core, err := NewCore(vpn, kubeFake, metadataFake, ccmFake, cnmFake, autoscalerFake, encryptedDiskFake, zapLogger, vtpm.OpenSimulatedTPM, &fakeStoreFactory{}, file.NewHandler(afero.NewMemMapFs())) if err != nil { return nil, nil, err } diff --git a/coordinator/core/peer_test.go b/coordinator/core/peer_test.go index 66ac0cce3..f5823c1b8 100644 --- a/coordinator/core/peer_test.go +++ b/coordinator/core/peer_test.go @@ -53,7 +53,7 @@ func TestGetPeers(t *testing.T) { assert := assert.New(t) require := require.New(t) - core, err := NewCore(&stubVPN{}, nil, nil, nil, nil, nil, zaptest.NewLogger(t), nil, nil, file.NewHandler(afero.NewMemMapFs())) + core, err := NewCore(&stubVPN{}, nil, nil, nil, nil, nil, nil, zaptest.NewLogger(t), nil, nil, file.NewHandler(afero.NewMemMapFs())) require.NoError(err) // prepare store @@ -113,7 +113,7 @@ func TestAddPeer(t *testing.T) { assert := assert.New(t) require := require.New(t) - core, err := NewCore(&tc.vpn, nil, nil, nil, nil, nil, zaptest.NewLogger(t), nil, nil, file.NewHandler(afero.NewMemMapFs())) + core, err := NewCore(&tc.vpn, nil, nil, nil, nil, nil, nil, zaptest.NewLogger(t), nil, nil, file.NewHandler(afero.NewMemMapFs())) require.NoError(err) err = core.AddPeer(tc.peer) diff --git a/coordinator/core/state_test.go b/coordinator/core/state_test.go index a3f6d4edc..6db55cb20 100644 --- a/coordinator/core/state_test.go +++ b/coordinator/core/state_test.go @@ -65,7 +65,7 @@ func TestAdvanceState(t *testing.T) { return vtpm.OpenSimulatedTPM() } - core, err := NewCore(&stubVPN{}, nil, nil, nil, nil, nil, zaptest.NewLogger(t), openTPM, nil, file.NewHandler(afero.NewMemMapFs())) + core, err := NewCore(&stubVPN{}, nil, nil, nil, nil, nil, nil, zaptest.NewLogger(t), openTPM, nil, file.NewHandler(afero.NewMemMapFs())) require.NoError(err) assert.Equal(state.Uninitialized, core.GetState()) core.state = tc.initialState diff --git a/coordinator/pubapi/core.go b/coordinator/pubapi/core.go index 4614ad758..e0f2acb39 100644 --- a/coordinator/pubapi/core.go +++ b/coordinator/pubapi/core.go @@ -23,6 +23,8 @@ type Core interface { SetUpKMS(ctx context.Context, storageURI, kmsURI, kekID string, useExisting bool) error GetKMSInfo() (kms.KMSInformation, error) GetDataKey(ctx context.Context, keyID string, length int) ([]byte, error) + GetDiskUUID() (string, error) + UpdateDiskPassphrase(passphrase string) error GetState() state.State RequireState(...state.State) error diff --git a/coordinator/pubapi/core_test.go b/coordinator/pubapi/core_test.go index cc044d8b8..3e67423cf 100644 --- a/coordinator/pubapi/core_test.go +++ b/coordinator/pubapi/core_test.go @@ -145,3 +145,11 @@ func (c *fakeCore) GetKMSInfo() (kms.KMSInformation, error) { func (c *fakeCore) GetDataKey(ctx context.Context, keyID string, length int) ([]byte, error) { return c.dataKey, c.getDataKeyErr } + +func (c *fakeCore) GetDiskUUID() (string, error) { + return "fake-disk-uuid", nil +} + +func (c *fakeCore) UpdateDiskPassphrase(passphrase string) error { + return nil +}