// UpdateCluster updates a Cluster referenced by ClusterID with the given ClusterSpec. // - Returns `NotFound` if the Cluster is not found. // - Returns `InvalidArgument` if the ClusterSpec is malformed. // - Returns `Unimplemented` if the ClusterSpec references unimplemented features. // - Returns an error if the update fails. func (s *Server) UpdateCluster(ctx context.Context, request *api.UpdateClusterRequest) (*api.UpdateClusterResponse, error) { if request.ClusterID == "" || request.ClusterVersion == nil { return nil, grpc.Errorf(codes.InvalidArgument, errInvalidArgument.Error()) } if err := validateClusterSpec(request.Spec); err != nil { return nil, err } var cluster *api.Cluster err := s.store.Update(func(tx store.Tx) error { cluster = store.GetCluster(tx, request.ClusterID) if cluster == nil { return nil } cluster.Meta.Version = *request.ClusterVersion cluster.Spec = *request.Spec.Copy() return store.UpdateCluster(tx, cluster) }) if err != nil { return nil, err } if cluster == nil { return nil, grpc.Errorf(codes.NotFound, "cluster %s not found", request.ClusterID) } redactedClusters := redactClusters([]*api.Cluster{cluster}) // WARN: we should never return cluster here. We need to redact the private fields first. return &api.UpdateClusterResponse{ Cluster: redactedClusters[0], }, nil }
func (k *KeyManager) updateKey(cluster *api.Cluster) error { return k.store.Update(func(tx store.Tx) error { cluster = store.GetCluster(tx, cluster.ID) if cluster == nil { return nil } cluster.EncryptionKeyLamportClock = k.keyRing.lClock cluster.NetworkBootstrapKeys = k.keyRing.keys return store.UpdateCluster(tx, cluster) }) }
// NewUpdater creates a new Updater. func NewUpdater(store *store.MemoryStore, restartSupervisor *RestartSupervisor, cluster *api.Cluster, newService *api.Service) *Updater { return &Updater{ store: store, watchQueue: store.WatchQueue(), restarts: restartSupervisor, cluster: cluster.Copy(), newService: newService.Copy(), stopChan: make(chan struct{}), doneChan: make(chan struct{}), } }
func TestGetUnlockKey(t *testing.T) { t.Parallel() tc := testutils.NewTestCA(t) defer tc.Stop() var cluster *api.Cluster tc.MemoryStore.View(func(tx store.ReadTx) { clusters, err := store.FindClusters(tx, store.ByName(store.DefaultClusterName)) require.NoError(t, err) cluster = clusters[0] }) resp, err := tc.CAClients[0].GetUnlockKey(context.Background(), &api.GetUnlockKeyRequest{}) require.NoError(t, err) require.Nil(t, resp.UnlockKey) require.Equal(t, cluster.Meta.Version, resp.Version) // Update the unlock key require.NoError(t, tc.MemoryStore.Update(func(tx store.Tx) error { cluster = store.GetCluster(tx, cluster.ID) cluster.Spec.EncryptionConfig.AutoLockManagers = true cluster.UnlockKeys = []*api.EncryptionKey{{ Subsystem: ca.ManagerRole, Key: []byte("secret"), }} return store.UpdateCluster(tx, cluster) })) tc.MemoryStore.View(func(tx store.ReadTx) { cluster = store.GetCluster(tx, cluster.ID) }) require.NoError(t, raftutils.PollFuncWithTimeout(nil, func() error { resp, err = tc.CAClients[0].GetUnlockKey(context.Background(), &api.GetUnlockKeyRequest{}) if err != nil { return fmt.Errorf("get unlock key: %v", err) } if !bytes.Equal(resp.UnlockKey, []byte("secret")) { return fmt.Errorf("secret hasn't rotated yet") } if cluster.Meta.Version.Index > resp.Version.Index { return fmt.Errorf("hasn't updated to the right version yet") } return nil }, 250*time.Millisecond)) }
// UpdateCluster updates a Cluster referenced by ClusterID with the given ClusterSpec. // - Returns `NotFound` if the Cluster is not found. // - Returns `InvalidArgument` if the ClusterSpec is malformed. // - Returns `Unimplemented` if the ClusterSpec references unimplemented features. // - Returns an error if the update fails. func (s *Server) UpdateCluster(ctx context.Context, request *api.UpdateClusterRequest) (*api.UpdateClusterResponse, error) { if request.ClusterID == "" || request.ClusterVersion == nil { return nil, grpc.Errorf(codes.InvalidArgument, errInvalidArgument.Error()) } if err := validateClusterSpec(request.Spec); err != nil { return nil, err } var cluster *api.Cluster err := s.store.Update(func(tx store.Tx) error { cluster = store.GetCluster(tx, request.ClusterID) if cluster == nil { return nil } cluster.Meta.Version = *request.ClusterVersion cluster.Spec = *request.Spec.Copy() expireBlacklistedCerts(cluster) if request.Rotation.WorkerJoinToken { cluster.RootCA.JoinTokens.Worker = ca.GenerateJoinToken(s.rootCA) } if request.Rotation.ManagerJoinToken { cluster.RootCA.JoinTokens.Manager = ca.GenerateJoinToken(s.rootCA) } var unlockKeys []*api.EncryptionKey var managerKey *api.EncryptionKey for _, eKey := range cluster.UnlockKeys { if eKey.Subsystem == ca.ManagerRole { if !cluster.Spec.EncryptionConfig.AutoLockManagers { continue } managerKey = eKey } unlockKeys = append(unlockKeys, eKey) } switch { case !cluster.Spec.EncryptionConfig.AutoLockManagers: break case managerKey == nil: unlockKeys = append(unlockKeys, &api.EncryptionKey{ Subsystem: ca.ManagerRole, Key: encryption.GenerateSecretKey(), }) case request.Rotation.ManagerUnlockKey: managerKey.Key = encryption.GenerateSecretKey() } cluster.UnlockKeys = unlockKeys return store.UpdateCluster(tx, cluster) }) if err != nil { return nil, err } if cluster == nil { return nil, grpc.Errorf(codes.NotFound, "cluster %s not found", request.ClusterID) } redactedClusters := redactClusters([]*api.Cluster{cluster}) // WARN: we should never return cluster here. We need to redact the private fields first. return &api.UpdateClusterResponse{ Cluster: redactedClusters[0], }, nil }