// UpdateSecret updates a Secret referenced by SecretID with the given SecretSpec. // - Returns `NotFound` if the Secret is not found. // - Returns `InvalidArgument` if the SecretSpec is malformed or anything other than Labels is changed // - Returns an error if the update fails. func (s *Server) UpdateSecret(ctx context.Context, request *api.UpdateSecretRequest) (*api.UpdateSecretResponse, error) { if request.SecretID == "" || request.SecretVersion == nil { return nil, grpc.Errorf(codes.InvalidArgument, errInvalidArgument.Error()) } var secret *api.Secret err := s.store.Update(func(tx store.Tx) error { secret = store.GetSecret(tx, request.SecretID) if secret == nil { return nil } if secret.Spec.Annotations.Name != request.Spec.Annotations.Name || request.Spec.Data != nil { return grpc.Errorf(codes.InvalidArgument, "only updates to Labels are allowed") } // We only allow updating Labels secret.Meta.Version = *request.SecretVersion secret.Spec.Annotations.Labels = request.Spec.Annotations.Labels return store.UpdateSecret(tx, secret) }) if err != nil { return nil, err } if secret == nil { return nil, grpc.Errorf(codes.NotFound, "secret %s not found", request.SecretID) } // WARN: we should never return the actual secret data here. We need to redact the private fields first. secret.Spec.Data = nil return &api.UpdateSecretResponse{ Secret: secret, }, nil }
// UpdateSecret updates a Secret referenced by SecretID with the given SecretSpec. // - Returns `NotFound` if the Secret is not found. // - Returns `InvalidArgument` if the SecretSpec is malformed or anything other than Labels is changed // - Returns an error if the update fails. func (s *Server) UpdateSecret(ctx context.Context, request *api.UpdateSecretRequest) (*api.UpdateSecretResponse, error) { if request.SecretID == "" || request.SecretVersion == nil { return nil, grpc.Errorf(codes.InvalidArgument, errInvalidArgument.Error()) } var secret *api.Secret err := s.store.Update(func(tx store.Tx) error { secret = store.GetSecret(tx, request.SecretID) if secret == nil { return nil } // Check if the Name is different than the current name, or the secret is non-nil and different // than the current secret if secret.Spec.Annotations.Name != request.Spec.Annotations.Name || (request.Spec.Data != nil && subtle.ConstantTimeCompare(request.Spec.Data, secret.Spec.Data) == 0) { return grpc.Errorf(codes.InvalidArgument, "only updates to Labels are allowed") } // We only allow updating Labels secret.Meta.Version = *request.SecretVersion secret.Spec.Annotations.Labels = request.Spec.Annotations.Labels return store.UpdateSecret(tx, secret) }) if err != nil { return nil, err } if secret == nil { return nil, grpc.Errorf(codes.NotFound, "secret %s not found", request.SecretID) } log.G(ctx).WithFields(logrus.Fields{ "secret.ID": request.SecretID, "secret.Name": request.Spec.Annotations.Name, "method": "UpdateSecret", }).Debugf("secret updated") // WARN: we should never return the actual secret data here. We need to redact the private fields first. secret.Spec.Data = nil return &api.UpdateSecretResponse{ Secret: secret, }, nil }
// If a secret is updated or deleted, even if it's for an existing task, no changes will be sent down func TestAssignmentsSecretUpdateAndDeletion(t *testing.T) { t.Parallel() gd, err := startDispatcher(DefaultConfig()) assert.NoError(t, err) defer gd.Close() expectedSessionID, nodeID := getSessionAndNodeID(t, gd.Clients[0]) // create the relevant secrets and tasks secrets, tasks := makeTasksAndSecrets(t, nodeID) err = gd.Store.Update(func(tx store.Tx) error { for _, secret := range secrets[:len(secrets)-1] { assert.NoError(t, store.CreateSecret(tx, secret)) } for _, task := range tasks { assert.NoError(t, store.CreateTask(tx, task)) } return nil }) assert.NoError(t, err) stream, err := gd.Clients[0].Assignments(context.Background(), &api.AssignmentsRequest{SessionID: expectedSessionID}) assert.NoError(t, err) defer stream.CloseSend() time.Sleep(100 * time.Millisecond) // check the initial task and secret stream resp, err := stream.Recv() assert.NoError(t, err) // FIXME(aaronl): This is hard to maintain. assert.Equal(t, 16, len(resp.Changes)) taskChanges, secretChanges := collectTasksAndSecrets(resp.Changes) assert.Len(t, taskChanges, 10) // 10 types of task states >= assigned, 2 types < assigned for _, task := range tasks[2:] { assert.NotNil(t, taskChanges[idAndAction{id: task.ID, action: api.AssignmentChange_AssignmentActionUpdate}]) } assert.Len(t, secretChanges, 6) // 6 types of task states between assigned and running inclusive for _, secret := range secrets[2:8] { assert.NotNil(t, secretChanges[idAndAction{id: secret.ID, action: api.AssignmentChange_AssignmentActionUpdate}]) } // updating secrets, used by tasks or not, do not cause any changes err = gd.Store.Update(func(tx store.Tx) error { for _, secret := range secrets[:len(secrets)-2] { secret.Spec.Data = []byte("new secret data") assert.NoError(t, store.UpdateSecret(tx, secret)) } return nil }) assert.NoError(t, err) recvChan := make(chan struct{}) go func() { _, _ = stream.Recv() recvChan <- struct{}{} }() select { case <-recvChan: assert.Fail(t, "secret update should not trigger dispatcher update") case <-time.After(250 * time.Millisecond): } // deleting secrets, used by tasks or not, do not cause any changes err = gd.Store.Update(func(tx store.Tx) error { for _, secret := range secrets[:len(secrets)-2] { assert.NoError(t, store.DeleteSecret(tx, secret.ID)) } return nil }) assert.NoError(t, err) select { case <-recvChan: assert.Fail(t, "secret delete should not trigger dispatcher update") case <-time.After(250 * time.Millisecond): } }