Skip to content

Commit 885de7b

Browse files
authored
fix dynamoevents iterator compat (#50820)
1 parent a0a1b87 commit 885de7b

File tree

2 files changed

+74
-0
lines changed

2 files changed

+74
-0
lines changed

lib/events/dynamoevents/dynamoevents.go

+58
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ import (
4242
autoscalingtypes "github.com/aws/aws-sdk-go-v2/service/applicationautoscaling/types"
4343
"github.com/aws/aws-sdk-go-v2/service/dynamodb"
4444
dynamodbtypes "github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
45+
legacydynamo "github.com/aws/aws-sdk-go/service/dynamodb"
46+
"github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute"
4547
"github.com/aws/smithy-go"
4648
"github.com/google/uuid"
4749
"github.com/gravitational/trace"
@@ -689,6 +691,24 @@ type checkpointKey struct {
689691
EventKey string `json:"event_key,omitempty"`
690692
}
691693

694+
// legacyCheckpointKey is the old checkpoint key returned by older auth versions. Used to decode
695+
// checkpoints originating from old auths. Commonly we don't bother supporting pagination/cursors
696+
// across teleport versions since the benefit of doing so is usually minimal, but this value is used
697+
// as on-disk state by long running event export operations, and so must be supported.
698+
//
699+
// DELETE IN: 19.0.0
700+
type legacyCheckpointKey struct {
701+
// The date that the Dynamo iterator corresponds to.
702+
Date string `json:"date,omitempty"`
703+
704+
// A DynamoDB query iterator. Allows us to resume a partial query.
705+
Iterator map[string]*legacydynamo.AttributeValue `json:"iterator,omitempty"`
706+
707+
// EventKey is a derived identifier for an event used for resuming
708+
// sub-page breaks due to size constraints.
709+
EventKey string `json:"event_key,omitempty"`
710+
}
711+
692712
// SearchEvents is a flexible way to find events.
693713
//
694714
// Event types to filter can be specified and pagination is handled by an iterator key that allows
@@ -936,11 +956,49 @@ func getCheckpointFromStartKey(startKey string) (checkpointKey, error) {
936956
}
937957
// If a checkpoint key is provided, unmarshal it so we can work with it's parts.
938958
if err := json.Unmarshal([]byte(startKey), &checkpoint); err != nil {
959+
// attempt to decode as legacy format.
960+
if checkpoint, err = getCheckpointFromLegacyStartKey(startKey); err == nil {
961+
return checkpoint, nil
962+
}
939963
return checkpointKey{}, trace.Wrap(err)
940964
}
941965
return checkpoint, nil
942966
}
943967

968+
// getCheckpointFromLegacyStartKey is a helper function that decodes a legacy checkpoint key
969+
// into the new format. The old format used raw dynamo attribute values for the iterator, where
970+
// the new format uses a json-serialized map with bare values.
971+
//
972+
// DELETE IN: 19.0.0
973+
func getCheckpointFromLegacyStartKey(startKey string) (checkpointKey, error) {
974+
var checkpoint legacyCheckpointKey
975+
if startKey == "" {
976+
return checkpointKey{}, nil
977+
}
978+
// If a checkpoint key is provided, unmarshal it so we can work with its parts.
979+
if err := json.Unmarshal([]byte(startKey), &checkpoint); err != nil {
980+
return checkpointKey{}, trace.Wrap(err)
981+
}
982+
983+
// decode the dynamo attrs into the go map repr common to the old and new formats.
984+
m := make(map[string]any)
985+
if err := dynamodbattribute.UnmarshalMap(checkpoint.Iterator, &m); err != nil {
986+
return checkpointKey{}, trace.Wrap(err)
987+
}
988+
989+
// encode the map into json, making it equivalent to the new format.
990+
iterator, err := json.Marshal(m)
991+
if err != nil {
992+
return checkpointKey{}, trace.Wrap(err)
993+
}
994+
995+
return checkpointKey{
996+
Date: checkpoint.Date,
997+
Iterator: string(iterator),
998+
EventKey: checkpoint.EventKey,
999+
}, nil
1000+
}
1001+
9441002
func getExprFilter(filter searchEventsFilter) *string {
9451003
var filterConds []string
9461004
if len(filter.eventTypes) > 0 {

lib/events/dynamoevents/dynamoevents_test.go

+16
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import (
3333
"time"
3434

3535
"github.com/aws/aws-sdk-go-v2/aws"
36+
"github.com/google/go-cmp/cmp"
3637
"github.com/google/uuid"
3738
"github.com/gravitational/trace"
3839
"github.com/jonboulle/clockwork"
@@ -666,3 +667,18 @@ func TestEndpoints(t *testing.T) {
666667
})
667668
}
668669
}
670+
671+
func TestStartKeyBackCompat(t *testing.T) {
672+
const (
673+
oldStartKey = `{"date":"2023-04-27","iterator":{"CreatedAt":{"B":null,"BOOL":null,"BS":null,"L":null,"M":null,"N":"1682583778","NS":null,"NULL":null,"S":null,"SS":null},"CreatedAtDate":{"B":null,"BOOL":null,"BS":null,"L":null,"M":null,"N":null,"NS":null,"NULL":null,"S":"2023-04-27","SS":null},"EventIndex":{"B":null,"BOOL":null,"BS":null,"L":null,"M":null,"N":"0","NS":null,"NULL":null,"S":null,"SS":null},"SessionID":{"B":null,"BOOL":null,"BS":null,"L":null,"M":null,"N":null,"NS":null,"NULL":null,"S":"4bc51fd7-4f0c-47ee-b9a5-da621fbdbabb","SS":null}}}`
674+
newStartKey = `{"date":"2023-04-27","iterator":"{\"CreatedAt\":1682583778,\"CreatedAtDate\":\"2023-04-27\",\"EventIndex\":0,\"SessionID\":\"4bc51fd7-4f0c-47ee-b9a5-da621fbdbabb\"}"}`
675+
)
676+
677+
oldCP, err := getCheckpointFromStartKey(oldStartKey)
678+
require.NoError(t, err)
679+
680+
newCP, err := getCheckpointFromStartKey(newStartKey)
681+
require.NoError(t, err)
682+
683+
require.Empty(t, cmp.Diff(oldCP, newCP))
684+
}

0 commit comments

Comments
 (0)