feat: persist fenced client command events
This commit is contained in:
@@ -220,12 +220,16 @@ func validateCommandAccepted(accepted *rvboxv1.CommandAccepted, limits Limits) e
|
||||
}
|
||||
|
||||
func validateCommandEvent(event *rvboxv1.CommandEvent, limits Limits) error {
|
||||
if event == nil || event.Payload == nil || event.ObservedAt == nil || event.ObservedAt.CheckValid() != nil {
|
||||
if event == nil || event.Payload == nil || event.ObservedAt == nil || event.ObservedAt.CheckValid() != nil || len(event.ImmutableEventSha256) != sha256.Size {
|
||||
return fmt.Errorf("%w: invalid command event", ErrInvalidEnvelope)
|
||||
}
|
||||
if err := validateIssueAndSequence(event.IssueUuid, event.EventSeq); err != nil {
|
||||
return err
|
||||
}
|
||||
digest, err := CommandEventDigest(event)
|
||||
if err != nil || string(digest[:]) != string(event.ImmutableEventSha256) {
|
||||
return fmt.Errorf("%w: command event immutable digest mismatch", ErrInvalidEnvelope)
|
||||
}
|
||||
switch payload := event.Payload.(type) {
|
||||
case *rvboxv1.CommandEvent_Output:
|
||||
_, err := DecodeOutputChunk(payload.Output, limits.MaxRawChunkBytes)
|
||||
@@ -265,6 +269,22 @@ func validateCommandEvent(event *rvboxv1.CommandEvent, limits Limits) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// CommandEventDigest is the canonical retransmission digest: protobuf bytes of
|
||||
// the event with its digest field cleared. Producers populate the field with
|
||||
// this value; consumers verify it before using event_seq for idempotency.
|
||||
func CommandEventDigest(event *rvboxv1.CommandEvent) ([sha256.Size]byte, error) {
|
||||
if event == nil {
|
||||
return [sha256.Size]byte{}, ErrInvalidEnvelope
|
||||
}
|
||||
copyEvent := proto.Clone(event).(*rvboxv1.CommandEvent)
|
||||
copyEvent.ImmutableEventSha256 = nil
|
||||
encoded, err := proto.MarshalOptions{Deterministic: true}.Marshal(copyEvent)
|
||||
if err != nil {
|
||||
return [sha256.Size]byte{}, err
|
||||
}
|
||||
return sha256.Sum256(encoded), nil
|
||||
}
|
||||
|
||||
func validateScriptDescriptor(descriptor *rvboxv1.ScriptDescriptor, limits Limits) error {
|
||||
if descriptor == nil || descriptor.Filename == "" || len(descriptor.Filename) > 255 || descriptor.SizeBytes > limits.MaxScriptBytes || len(descriptor.Sha256) != sha256.Size || !utf8.ValidString(descriptor.Filename) {
|
||||
return fmt.Errorf("%w: invalid script descriptor", ErrInvalidExecutionSpec)
|
||||
|
||||
@@ -209,7 +209,12 @@ func TestOutputChunkBoundedDecompression_HP_PROTO_05(t *testing.T) {
|
||||
func TestCommandEventPayloadValidation_BH_OUTFLOW_01(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
event := &rvboxv1.CommandEvent{IssueUuid: testIssueUUID, EventSeq: 1, ObservedAt: fixedTimestamp(), Payload: &rvboxv1.CommandEvent_Lifecycle{Lifecycle: &rvboxv1.LifecycleChange{Lifecycle: rvboxv1.CommandLifecycle_COMMAND_RUNNING, CommandRevision: 1}}}
|
||||
event := &rvboxv1.CommandEvent{IssueUuid: testIssueUUID, EventSeq: 1, ObservedAt: fixedTimestamp(), Payload: &rvboxv1.CommandEvent_Lifecycle{Lifecycle: &rvboxv1.LifecycleChange{Lifecycle: rvboxv1.CommandLifecycle_COMMAND_RUNNING, CommandRevision: 1}}, ImmutableEventSha256: make([]byte, sha256.Size)}
|
||||
digest, err := CommandEventDigest(event)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
event.ImmutableEventSha256 = digest[:]
|
||||
envelope := &rvboxv1.AgentEnvelope{SessionId: "session", SessionGeneration: 1, Payload: &rvboxv1.AgentEnvelope_CommandEvent{CommandEvent: event}}
|
||||
if err := ValidateEnvelope(envelope, DefaultLimits(), rvboxv1.Platform_PLATFORM_WINDOWS); err != nil {
|
||||
t.Fatalf("valid event: %v", err)
|
||||
|
||||
Reference in New Issue
Block a user