From e3db8dd049584b273f825f3cdfcb8df19e6e6196 Mon Sep 17 00:00:00 2001 From: xacrimon Date: Wed, 12 May 2021 14:08:11 +0200 Subject: [PATCH 01/28] implement rfd 19 --- api/client/client.go | 47 + api/client/proto/authservice.pb.go | 1835 +++++++++++++---- api/client/proto/authservice.proto | 41 + e | 2 +- integration/integration_test.go | 4 +- lib/auth/apiserver.go | 11 +- lib/auth/auth_with_roles.go | 44 +- lib/auth/clt.go | 47 +- lib/auth/grpcserver.go | 49 + lib/auth/tls_test.go | 5 +- lib/events/api.go | 19 +- lib/events/auditlog.go | 45 +- lib/events/auditlog_test.go | 4 +- lib/events/discard.go | 8 +- lib/events/dynamoevents/dynamoevents.go | 246 ++- lib/events/dynamoevents/dynamoevents_test.go | 68 +- lib/events/filelog.go | 66 +- lib/events/firestoreevents/firestoreevents.go | 66 +- lib/events/forward.go | 15 +- lib/events/multilog.go | 31 +- lib/events/test/suite.go | 38 +- lib/events/writer.go | 24 +- lib/srv/exec_test.go | 8 +- lib/web/apiserver.go | 14 +- rfd/0019-event-iteration-api.md | 2 +- 25 files changed, 2091 insertions(+), 648 deletions(-) diff --git a/api/client/client.go b/api/client/client.go index d9e4445757e73..3d8cc6eb84776 100644 --- a/api/client/client.go +++ b/api/client/client.go @@ -20,6 +20,7 @@ import ( "compress/gzip" "context" "crypto/tls" + "encoding/json" "io" "net" "sync" @@ -1293,6 +1294,52 @@ func (c *Client) DeleteAllNodes(ctx context.Context, namespace string) error { return trail.FromGRPC(err) } +// SearchEvents allows searching for events with a full pagination support. +func (c *Client) SearchEvents(ctx context.Context, fromUTC, toUTC time.Time, namespace string, eventTypes []string, limit int, startKey string) ([]map[string]interface{}, string, error) { + request := &proto.GetEventsRequest{ + Namespace: namespace, + StartDate: fromUTC, + EndDate: toUTC, + EventTypes: eventTypes, + Limit: int32(limit), + StartKey: startKey, + } + + response, err := c.grpc.GetEvents(ctx, request) + if err != nil { + return nil, "", trail.FromGRPC(err) + } + + var decodedEvents []map[string]interface{} + if err := json.Unmarshal(response.Items, &decodedEvents); err != nil { + return nil, "", trace.Wrap(err) + } + + return decodedEvents, response.LastKey, nil +} + +// SearchSessionEvents allows searching for session events with a full pagination support. +func (c *Client) SearchSessionEvents(ctx context.Context, fromUTC time.Time, toUTC time.Time, limit int, startKey string) ([]map[string]interface{}, string, error) { + request := &proto.GetSessionEventsRequest{ + StartDate: fromUTC, + EndDate: toUTC, + Limit: int32(limit), + StartKey: startKey, + } + + response, err := c.grpc.GetSessionEvents(ctx, request) + if err != nil { + return nil, "", trail.FromGRPC(err) + } + + var decodedEvents []map[string]interface{} + if err := json.Unmarshal(response.Items, &decodedEvents); err != nil { + return nil, "", trace.Wrap(err) + } + + return decodedEvents, response.LastKey, nil +} + // GetClusterNetworkingConfig gets cluster networking configuration. func (c *Client) GetClusterNetworkingConfig(ctx context.Context) (types.ClusterNetworkingConfig, error) { resp, err := c.grpc.GetClusterNetworkingConfig(ctx, &empty.Empty{}, c.callOpts...) diff --git a/api/client/proto/authservice.pb.go b/api/client/proto/authservice.pb.go index a2d6cfd3feab0..0e476434a7116 100644 --- a/api/client/proto/authservice.pb.go +++ b/api/client/proto/authservice.pb.go @@ -3594,7 +3594,7 @@ func (m *DatabaseCSRRequest) GetCSR() []byte { if m != nil { return m.CSR } - return nil + return nil } func (m *DatabaseCSRRequest) GetClusterName() string { @@ -5893,6 +5893,230 @@ func (*SingleUseUserCert) XXX_OneofWrappers() []interface{} { } } +type GetEventsRequest struct { + // Namespace, if not set, defaults to 'default' + Namespace string `protobuf:"bytes,1,opt,name=Namespace,proto3" json:"Namespace,omitempty"` + // Oldest date of returned events + StartDate time.Time `protobuf:"bytes,2,opt,name=StartDate,proto3,stdtime" json:"StartDate"` + // Newest date of returned events + EndDate time.Time `protobuf:"bytes,3,opt,name=EndDate,proto3,stdtime" json:"EndDate"` + // EventTypes is optional, if not set, returns all events + EventTypes []string `protobuf:"bytes,4,rep,name=EventTypes,proto3" json:"EventTypes,omitempty"` + // Maximum amount of events returned + Limit int32 `protobuf:"varint,5,opt,name=Limit,proto3" json:"Limit,omitempty"` + // When supplied the search will resume from the last key + StartKey string `protobuf:"bytes,6,opt,name=StartKey,proto3" json:"StartKey,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetEventsRequest) Reset() { *m = GetEventsRequest{} } +func (m *GetEventsRequest) String() string { return proto.CompactTextString(m) } +func (*GetEventsRequest) ProtoMessage() {} +func (*GetEventsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_ce8bd90b12161215, []int{89} +} +func (m *GetEventsRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *GetEventsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_GetEventsRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *GetEventsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetEventsRequest.Merge(m, src) +} +func (m *GetEventsRequest) XXX_Size() int { + return m.Size() +} +func (m *GetEventsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetEventsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetEventsRequest proto.InternalMessageInfo + +func (m *GetEventsRequest) GetNamespace() string { + if m != nil { + return m.Namespace + } + return "" +} + +func (m *GetEventsRequest) GetStartDate() time.Time { + if m != nil { + return m.StartDate + } + return time.Time{} +} + +func (m *GetEventsRequest) GetEndDate() time.Time { + if m != nil { + return m.EndDate + } + return time.Time{} +} + +func (m *GetEventsRequest) GetEventTypes() []string { + if m != nil { + return m.EventTypes + } + return nil +} + +func (m *GetEventsRequest) GetLimit() int32 { + if m != nil { + return m.Limit + } + return 0 +} + +func (m *GetEventsRequest) GetStartKey() string { + if m != nil { + return m.StartKey + } + return "" +} + +type GetSessionEventsRequest struct { + // Oldest date of returned events + StartDate time.Time `protobuf:"bytes,1,opt,name=StartDate,proto3,stdtime" json:"StartDate"` + // Newest date of returned events + EndDate time.Time `protobuf:"bytes,2,opt,name=EndDate,proto3,stdtime" json:"EndDate"` + Limit int32 `protobuf:"varint,3,opt,name=Limit,proto3" json:"Limit,omitempty"` + StartKey string `protobuf:"bytes,4,opt,name=StartKey,proto3" json:"StartKey,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetSessionEventsRequest) Reset() { *m = GetSessionEventsRequest{} } +func (m *GetSessionEventsRequest) String() string { return proto.CompactTextString(m) } +func (*GetSessionEventsRequest) ProtoMessage() {} +func (*GetSessionEventsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_ce8bd90b12161215, []int{90} +} +func (m *GetSessionEventsRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *GetSessionEventsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_GetSessionEventsRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *GetSessionEventsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetSessionEventsRequest.Merge(m, src) +} +func (m *GetSessionEventsRequest) XXX_Size() int { + return m.Size() +} +func (m *GetSessionEventsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetSessionEventsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetSessionEventsRequest proto.InternalMessageInfo + +func (m *GetSessionEventsRequest) GetStartDate() time.Time { + if m != nil { + return m.StartDate + } + return time.Time{} +} + +func (m *GetSessionEventsRequest) GetEndDate() time.Time { + if m != nil { + return m.EndDate + } + return time.Time{} +} + +func (m *GetSessionEventsRequest) GetLimit() int32 { + if m != nil { + return m.Limit + } + return 0 +} + +func (m *GetSessionEventsRequest) GetStartKey() string { + if m != nil { + return m.StartKey + } + return "" +} + +type Events struct { + Items []byte `protobuf:"bytes,1,opt,name=Items,proto3" json:"Items,omitempty"` + // the key of the last event if the returned set did not contain all events found i.e limit < + // actual amount. this is the key clients can supply in another API request to continue fetching + // events from the previous last position + LastKey string `protobuf:"bytes,2,opt,name=LastKey,proto3" json:"LastKey,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Events) Reset() { *m = Events{} } +func (m *Events) String() string { return proto.CompactTextString(m) } +func (*Events) ProtoMessage() {} +func (*Events) Descriptor() ([]byte, []int) { + return fileDescriptor_ce8bd90b12161215, []int{91} +} +func (m *Events) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Events) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Events.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Events) XXX_Merge(src proto.Message) { + xxx_messageInfo_Events.Merge(m, src) +} +func (m *Events) XXX_Size() int { + return m.Size() +} +func (m *Events) XXX_DiscardUnknown() { + xxx_messageInfo_Events.DiscardUnknown(m) +} + +var xxx_messageInfo_Events proto.InternalMessageInfo + +func (m *Events) GetItems() []byte { + if m != nil { + return m.Items + } + return nil +} + +func (m *Events) GetLastKey() string { + if m != nil { + return m.LastKey + } + return "" +} + func init() { proto.RegisterEnum("proto.Operation", Operation_name, Operation_value) proto.RegisterEnum("proto.UserCertsRequest_CertUsage", UserCertsRequest_CertUsage_name, UserCertsRequest_CertUsage_value) @@ -5987,344 +6211,358 @@ func init() { proto.RegisterType((*NodeLogin)(nil), "proto.NodeLogin") proto.RegisterType((*IsMFARequiredResponse)(nil), "proto.IsMFARequiredResponse") proto.RegisterType((*SingleUseUserCert)(nil), "proto.SingleUseUserCert") + proto.RegisterType((*GetEventsRequest)(nil), "proto.GetEventsRequest") + proto.RegisterType((*GetSessionEventsRequest)(nil), "proto.GetSessionEventsRequest") + proto.RegisterType((*Events)(nil), "proto.Events") } func init() { proto.RegisterFile("authservice.proto", fileDescriptor_ce8bd90b12161215) } var fileDescriptor_ce8bd90b12161215 = []byte{ - // 5306 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x3c, 0x4d, 0x73, 0x1b, 0x57, - 0x72, 0x1c, 0xf0, 0x0b, 0x68, 0x7e, 0x81, 0x8f, 0x5f, 0x10, 0x44, 0x11, 0xf2, 0x68, 0xd7, 0x2b, - 0x7b, 0xbd, 0xa4, 0x17, 0xb4, 0x63, 0xaf, 0x65, 0x5b, 0x01, 0xc0, 0x0f, 0xc0, 0xa2, 0x24, 0x7a, - 0x00, 0x42, 0xd9, 0x8d, 0xab, 0x98, 0x21, 0xf0, 0x04, 0x4e, 0x11, 0xc4, 0x8c, 0x67, 0x06, 0x94, - 0x95, 0xd3, 0x1e, 0x76, 0xb7, 0x52, 0x49, 0xa5, 0x2a, 0x97, 0x1c, 0x72, 0x4a, 0xaa, 0x72, 0xdf, - 0x53, 0xae, 0x7b, 0x77, 0xa5, 0x2a, 0x1f, 0x97, 0x54, 0xaa, 0x72, 0x40, 0x36, 0x3e, 0xe2, 0x27, - 0xe4, 0x94, 0x7a, 0x5f, 0x33, 0xef, 0x0d, 0x66, 0x40, 0x4a, 0x56, 0x92, 0x8b, 0xc5, 0xe9, 0xd7, - 0xdd, 0xaf, 0x5f, 0x77, 0xbf, 0x7e, 0xfd, 0xfa, 0x35, 0x0c, 0xcb, 0x66, 0xdf, 0x3f, 0xf7, 0xb0, - 0x7b, 0x65, 0xb5, 0xf0, 0xb6, 0xe3, 0xda, 0xbe, 0x8d, 0xa6, 0xe9, 0x3f, 0xf9, 0xd5, 0x8e, 0xdd, - 0xb1, 0xe9, 0x9f, 0x3b, 0xe4, 0x2f, 0x36, 0x98, 0xbf, 0xdd, 0xb1, 0xed, 0x4e, 0x17, 0xef, 0xd0, - 0xaf, 0xb3, 0xfe, 0xf3, 0x1d, 0x7c, 0xe9, 0xf8, 0x2f, 0xf9, 0x60, 0x21, 0x3a, 0xe8, 0x5b, 0x97, - 0xd8, 0xf3, 0xcd, 0x4b, 0x87, 0x23, 0x7c, 0xd4, 0xb1, 0xfc, 0xf3, 0xfe, 0xd9, 0x76, 0xcb, 0xbe, - 0xdc, 0xe9, 0xb8, 0xe6, 0x95, 0xe5, 0x9b, 0xbe, 0x65, 0xf7, 0xcc, 0xee, 0x8e, 0x8f, 0xbb, 0xd8, - 0xb1, 0x5d, 0x7f, 0xc7, 0x74, 0xac, 0x1d, 0xff, 0xa5, 0x83, 0x3d, 0xf6, 0x5f, 0x4e, 0x58, 0x79, - 0x15, 0xc2, 0x17, 0xae, 0xe9, 0x38, 0xd8, 0x0d, 0xff, 0xe0, 0x4c, 0x1e, 0xbe, 0x0a, 0x13, 0x7c, - 0x85, 0x7b, 0xbe, 0xf8, 0x87, 0x31, 0xd0, 0xff, 0x79, 0x01, 0xa6, 0xf7, 0x09, 0x00, 0x7d, 0x0c, - 0x53, 0x8d, 0x97, 0x0e, 0xce, 0x69, 0x77, 0xb5, 0xfb, 0x8b, 0xc5, 0x2c, 0x1b, 0xdf, 0x7e, 0xea, - 0x60, 0x97, 0xb2, 0x2c, 0xa3, 0xe1, 0xa0, 0xb0, 0x48, 0x18, 0xbd, 0x67, 0x5f, 0x5a, 0x3e, 0xd5, - 0x91, 0x41, 0x29, 0xd0, 0x33, 0x58, 0x34, 0xb0, 0x67, 0xf7, 0xdd, 0x16, 0xae, 0x62, 0xb3, 0x8d, - 0xdd, 0x5c, 0xea, 0xae, 0x76, 0x7f, 0xae, 0xb8, 0xb6, 0xcd, 0xd6, 0xab, 0x0e, 0x96, 0xd7, 0x87, - 0x83, 0x02, 0x72, 0x39, 0x2c, 0x64, 0x56, 0x9d, 0x30, 0x22, 0x6c, 0xd0, 0x57, 0xb0, 0x50, 0xc1, - 0xae, 0x5f, 0xea, 0xfb, 0xe7, 0xb6, 0x6b, 0xf9, 0x2f, 0x73, 0x93, 0x94, 0xef, 0x3a, 0xe7, 0xab, - 0x8c, 0x35, 0x8b, 0xe5, 0xcd, 0xe1, 0xa0, 0x90, 0x6b, 0x61, 0xd7, 0x3f, 0x35, 0x05, 0x54, 0x61, - 0xaf, 0x32, 0x43, 0x7f, 0x04, 0xf3, 0x75, 0xa2, 0xae, 0x56, 0xc3, 0xbe, 0xc0, 0x3d, 0x2f, 0x37, - 0xa5, 0x08, 0x2d, 0x0f, 0x35, 0x8b, 0xe5, 0xdb, 0xc3, 0x41, 0x61, 0xc3, 0xa3, 0xb0, 0x53, 0x9f, - 0x02, 0x15, 0xd6, 0x0a, 0x27, 0xf4, 0x27, 0xb0, 0x78, 0xec, 0xda, 0x57, 0x96, 0x67, 0xd9, 0x3d, - 0x0a, 0xca, 0x4d, 0x53, 0xde, 0x1b, 0x9c, 0xb7, 0x3a, 0xd8, 0x2c, 0x96, 0xef, 0x0c, 0x07, 0x85, - 0x5b, 0x8e, 0x80, 0xb2, 0x09, 0x54, 0xcd, 0xa8, 0x24, 0xa8, 0x01, 0x73, 0x95, 0x6e, 0xdf, 0xf3, - 0xb1, 0xfb, 0xc4, 0xbc, 0xc4, 0xb9, 0x19, 0xca, 0x7e, 0x55, 0xe8, 0x25, 0x1c, 0x69, 0x16, 0xcb, - 0xf9, 0xe1, 0xa0, 0xb0, 0xde, 0x62, 0xa0, 0xd3, 0x9e, 0x79, 0xa9, 0xaa, 0x5c, 0x66, 0x43, 0xf5, - 0xcd, 0x3e, 0x2b, 0x76, 0xef, 0xb9, 0xd5, 0xc9, 0xcd, 0xaa, 0xfa, 0x96, 0xc7, 0x9a, 0xbb, 0x5c, - 0xdf, 0x9c, 0x73, 0x8b, 0x42, 0x23, 0xfa, 0x96, 0x09, 0xd0, 0x47, 0x30, 0x75, 0xe2, 0x61, 0x37, - 0x97, 0xa6, 0x4c, 0x17, 0x38, 0x53, 0x02, 0x6a, 0x16, 0x99, 0x77, 0xf5, 0x3d, 0xec, 0x2a, 0x1c, - 0x28, 0x01, 0x21, 0x34, 0xec, 0x2e, 0xce, 0x65, 0x14, 0x42, 0x02, 0x6a, 0xee, 0x32, 0x42, 0xd7, - 0xee, 0xaa, 0xcb, 0xa2, 0x04, 0xa8, 0x06, 0x19, 0xb2, 0x2e, 0xcf, 0x31, 0x5b, 0x38, 0x07, 0x94, - 0x3a, 0xcb, 0xa9, 0x03, 0x78, 0x79, 0x63, 0x38, 0x28, 0xac, 0xf4, 0xc4, 0xa7, 0xc2, 0x25, 0xa4, - 0x46, 0x0f, 0x61, 0xa6, 0x8e, 0xdd, 0x2b, 0xec, 0xe6, 0xe6, 0x28, 0x9f, 0x25, 0xe1, 0x26, 0x14, - 0xd8, 0x2c, 0x96, 0x57, 0x87, 0x83, 0x42, 0xd6, 0xa3, 0x5f, 0x0a, 0x0f, 0x4e, 0x46, 0x74, 0x6b, - 0xe0, 0x2b, 0xec, 0x7a, 0xb8, 0xd1, 0xef, 0xf5, 0x70, 0x37, 0x37, 0xaf, 0xe8, 0x56, 0x19, 0x13, - 0xbe, 0xec, 0x32, 0xe0, 0xa9, 0x4f, 0xa1, 0xaa, 0x6e, 0x15, 0x02, 0x74, 0x0e, 0x59, 0xf6, 0x57, - 0xc5, 0xee, 0xf5, 0x70, 0x8b, 0x6c, 0xd8, 0xdc, 0x02, 0x9d, 0xe0, 0x16, 0x9f, 0x20, 0x3a, 0xdc, - 0x2c, 0x96, 0x0b, 0xc3, 0x41, 0xe1, 0x36, 0xe3, 0x4d, 0xcc, 0xc7, 0x07, 0x94, 0x69, 0x46, 0xb8, - 0x92, 0x75, 0x94, 0x5a, 0x2d, 0xec, 0x79, 0x06, 0xfe, 0xba, 0x8f, 0x3d, 0x3f, 0xb7, 0xa8, 0xac, - 0x43, 0x19, 0x13, 0x3e, 0x62, 0x52, 0xe0, 0xa9, 0xcb, 0xa0, 0xea, 0x3a, 0x14, 0x02, 0x74, 0x0c, - 0x50, 0x72, 0x9c, 0x3a, 0xf6, 0x88, 0xab, 0xe7, 0x96, 0x28, 0xeb, 0x15, 0xce, 0xfa, 0x19, 0x3e, - 0xe3, 0x03, 0xcd, 0x62, 0xf9, 0xd6, 0x70, 0x50, 0x58, 0x33, 0x1d, 0xe7, 0xd4, 0x63, 0x20, 0x85, - 0xa9, 0xc4, 0x83, 0xe9, 0xfd, 0xd2, 0xf6, 0x31, 0x77, 0xc6, 0x5c, 0x36, 0xa2, 0x77, 0x69, 0x4c, - 0xc8, 0xeb, 0x52, 0xe0, 0x29, 0x77, 0xed, 0xa8, 0xde, 0x25, 0x02, 0xb2, 0xd3, 0xf7, 0x4c, 0xdf, - 0x3c, 0x33, 0x3d, 0xcc, 0xdd, 0x63, 0x59, 0xd9, 0xe9, 0xea, 0x60, 0x73, 0x97, 0xed, 0xf4, 0x36, - 0x87, 0x9e, 0xc6, 0xf8, 0x4b, 0x84, 0x1f, 0xd1, 0x48, 0xb8, 0xf0, 0x1c, 0xba, 0x46, 0x23, 0x2f, - 0xf0, 0x59, 0xbc, 0x46, 0x42, 0x54, 0x54, 0x85, 0xf4, 0x33, 0x7c, 0xc6, 0xe2, 0xd2, 0x0a, 0xe5, - 0xb7, 0x1c, 0xf2, 0x63, 0x11, 0x69, 0x97, 0xed, 0x0a, 0xc2, 0x6d, 0x34, 0x16, 0x05, 0xd4, 0xe8, - 0xd7, 0x1a, 0x6c, 0x88, 0xf8, 0x81, 0xfd, 0x17, 0xb6, 0x7b, 0x61, 0xf5, 0x3a, 0x3c, 0x74, 0xac, - 0x52, 0xce, 0x77, 0x23, 0x21, 0x29, 0x82, 0xd5, 0x2c, 0x96, 0x7f, 0x34, 0x1c, 0x14, 0xee, 0x05, - 0xe1, 0x29, 0x18, 0x8f, 0x8b, 0x27, 0x49, 0x73, 0x95, 0x01, 0xd2, 0xe2, 0xe4, 0xd0, 0xab, 0x30, - 0xfd, 0xcc, 0xf4, 0x5b, 0xe7, 0xe8, 0x21, 0x4c, 0x3f, 0xb2, 0x7a, 0x6d, 0x2f, 0xa7, 0xdd, 0x9d, - 0xa4, 0x1b, 0x9f, 0x1d, 0x68, 0x74, 0x90, 0x0c, 0x94, 0x37, 0xbe, 0x1d, 0x14, 0x26, 0x86, 0x83, - 0xc2, 0xd2, 0x05, 0x41, 0x93, 0x4e, 0x35, 0x46, 0xa7, 0xff, 0x43, 0x0a, 0x32, 0x01, 0x36, 0xda, - 0x84, 0x29, 0xf2, 0x2f, 0x3d, 0x1e, 0x33, 0xe5, 0xf4, 0x70, 0x50, 0x98, 0x22, 0x74, 0x06, 0x85, - 0xa2, 0x22, 0xcc, 0x1d, 0xd9, 0x66, 0xbb, 0x8e, 0x5b, 0x2e, 0xf6, 0x3d, 0x7a, 0xfe, 0xa5, 0xcb, - 0xd9, 0xe1, 0xa0, 0x30, 0xdf, 0xb5, 0xcd, 0xf6, 0xa9, 0xc7, 0xe0, 0x86, 0x8c, 0x44, 0x38, 0xd2, - 0xe0, 0x3d, 0x19, 0x72, 0x24, 0x61, 0xc8, 0xa0, 0x50, 0xf4, 0x05, 0xcc, 0x1c, 0x58, 0x5d, 0xe2, - 0xb0, 0x53, 0x54, 0xfe, 0xcd, 0xa8, 0xfc, 0xdb, 0x6c, 0x78, 0xbf, 0xe7, 0xbb, 0x2f, 0x59, 0xf4, - 0x79, 0x4e, 0x01, 0xd2, 0x42, 0x38, 0x07, 0xf4, 0x3e, 0xcc, 0xd6, 0xfb, 0x67, 0x54, 0xfc, 0x69, - 0x3a, 0x19, 0x3d, 0x82, 0xbd, 0xfe, 0xd9, 0x29, 0x59, 0x82, 0x44, 0x20, 0xd0, 0xf2, 0x3f, 0x83, - 0x39, 0x89, 0x3d, 0xca, 0xc2, 0xe4, 0x05, 0x7e, 0xc9, 0xd6, 0x6e, 0x90, 0x3f, 0xd1, 0x2a, 0x4c, - 0x5f, 0x99, 0xdd, 0x3e, 0xa6, 0x4b, 0xcd, 0x18, 0xec, 0xe3, 0x93, 0xd4, 0xc7, 0x9a, 0xfe, 0x25, - 0x4c, 0x93, 0x73, 0xd6, 0x43, 0xf7, 0x60, 0xb2, 0x5e, 0xaf, 0x52, 0xa2, 0xf9, 0xf2, 0xf2, 0x70, - 0x50, 0x58, 0xf0, 0xbc, 0x73, 0x69, 0x32, 0x32, 0x4a, 0x90, 0x1a, 0x47, 0x75, 0xca, 0x85, 0x23, - 0xf9, 0x5d, 0xd9, 0x16, 0x64, 0x54, 0xff, 0x97, 0x19, 0xc8, 0x92, 0x93, 0x80, 0xf2, 0x15, 0xa1, - 0xe2, 0x3d, 0xc8, 0x1c, 0xf7, 0xcf, 0xba, 0x56, 0xeb, 0x11, 0x97, 0x6c, 0xbe, 0xbc, 0x38, 0x1c, - 0x14, 0xc0, 0xa1, 0xc0, 0xd3, 0x0b, 0xfc, 0xd2, 0x08, 0x11, 0xd0, 0x7d, 0x48, 0x13, 0x0e, 0x44, - 0xc1, 0x4c, 0xe4, 0xf2, 0xfc, 0x70, 0x50, 0x48, 0xf7, 0x39, 0xcc, 0x08, 0x46, 0x51, 0x1d, 0x66, - 0xf7, 0xbf, 0x71, 0x2c, 0x17, 0x7b, 0x3c, 0xdd, 0xc8, 0x6f, 0xb3, 0x1c, 0x70, 0x5b, 0xe4, 0x80, - 0xdb, 0x0d, 0x91, 0x03, 0x96, 0xef, 0x70, 0x1f, 0x5a, 0xc6, 0x8c, 0x24, 0x94, 0xfc, 0xaf, 0xfe, - 0xb3, 0xa0, 0x19, 0x82, 0x13, 0x7a, 0x0f, 0x66, 0x0e, 0x6c, 0xf7, 0xd2, 0xf4, 0x69, 0x96, 0x91, - 0xe1, 0xf6, 0xa2, 0x10, 0xc5, 0x5e, 0x14, 0x82, 0x0e, 0x60, 0xd1, 0xb0, 0xfb, 0x3e, 0x6e, 0xd8, - 0x22, 0x68, 0x31, 0xb3, 0x6d, 0x0d, 0x07, 0x85, 0xbc, 0x4b, 0x46, 0x4e, 0x7d, 0x7b, 0x34, 0x3c, - 0x19, 0x11, 0x2a, 0xb4, 0x0f, 0x8b, 0x4a, 0x78, 0xf5, 0x72, 0x33, 0x77, 0x27, 0xef, 0x67, 0x58, - 0x10, 0x52, 0x83, 0xb2, 0xac, 0xf3, 0x08, 0x11, 0x7a, 0x02, 0xcb, 0x8f, 0xfa, 0x67, 0xd8, 0xed, - 0x61, 0x1f, 0x7b, 0x42, 0xa2, 0x59, 0x2a, 0xd1, 0xdd, 0xe1, 0xa0, 0xb0, 0x79, 0x11, 0x0c, 0xc6, - 0xc8, 0x34, 0x4a, 0x8a, 0x30, 0x2c, 0x71, 0x41, 0x45, 0xac, 0xe3, 0x39, 0xc1, 0x3a, 0xf7, 0xf1, - 0xc8, 0x68, 0xf9, 0x1e, 0xd7, 0xf2, 0xed, 0x60, 0xed, 0x22, 0x82, 0x4a, 0x13, 0x45, 0x79, 0xa2, - 0x5d, 0x48, 0x3f, 0xb1, 0xdb, 0x98, 0xee, 0xb1, 0x0c, 0x95, 0x96, 0x1d, 0xf5, 0x76, 0x1b, 0x47, - 0xf2, 0x20, 0x23, 0x40, 0x44, 0x47, 0x30, 0x7d, 0xe2, 0x99, 0x1d, 0x96, 0x2e, 0x2c, 0x16, 0xdf, - 0xe2, 0x12, 0x45, 0xbd, 0x8f, 0xe6, 0x9e, 0x14, 0xb1, 0xbc, 0x42, 0x42, 0x48, 0x9f, 0xfc, 0x29, - 0x87, 0x10, 0x3a, 0x86, 0xbe, 0x04, 0xe0, 0x52, 0x95, 0x1c, 0x87, 0x67, 0x0e, 0xcb, 0xea, 0x22, - 0x4b, 0x8e, 0x53, 0xde, 0xe2, 0xeb, 0x5b, 0x0f, 0xd6, 0x67, 0x3a, 0x8e, 0xc4, 0x4d, 0x62, 0xa2, - 0xef, 0x41, 0x26, 0x98, 0x1b, 0xcd, 0xc2, 0x64, 0xa9, 0xdb, 0xcd, 0x4e, 0x90, 0x3f, 0xea, 0xf5, - 0x6a, 0x56, 0x43, 0x8b, 0x00, 0xa1, 0xc2, 0xb3, 0x29, 0x34, 0x0f, 0x69, 0xa1, 0x90, 0xec, 0x24, - 0xc5, 0x77, 0x9c, 0xec, 0x94, 0xfe, 0xef, 0xda, 0x88, 0x0d, 0x48, 0x0c, 0xab, 0xb3, 0x5b, 0x13, - 0x55, 0x19, 0x0b, 0x74, 0x34, 0x86, 0xf1, 0xcb, 0x14, 0xd5, 0x9a, 0x21, 0x23, 0x91, 0x6d, 0x75, - 0x4c, 0x56, 0xd3, 0xb2, 0xbb, 0xf2, 0xb6, 0x72, 0x38, 0xcc, 0x08, 0x46, 0x51, 0x51, 0xda, 0x80, - 0x93, 0x61, 0x10, 0x12, 0x1b, 0x50, 0x36, 0x46, 0xb0, 0x15, 0x8b, 0xa1, 0xf0, 0x7c, 0xdf, 0x50, - 0x9a, 0x18, 0xe3, 0x07, 0x78, 0xfa, 0xef, 0x34, 0x59, 0xe7, 0x41, 0x90, 0xd5, 0x62, 0x83, 0xec, - 0x7b, 0x90, 0xe1, 0xa7, 0x62, 0x6d, 0x8f, 0xcb, 0x4f, 0x63, 0x08, 0x3f, 0x42, 0x4f, 0xad, 0xb6, - 0x11, 0x22, 0xa0, 0x1d, 0x00, 0x16, 0x50, 0x4a, 0xed, 0xb6, 0xcb, 0x17, 0xb1, 0x34, 0x1c, 0x14, - 0xe6, 0x78, 0xc8, 0x31, 0xdb, 0x6d, 0xd7, 0x90, 0x50, 0x88, 0x46, 0xe5, 0x2c, 0x7d, 0x2a, 0xd4, - 0xa8, 0x9c, 0x8f, 0x2b, 0x39, 0xb8, 0xde, 0x85, 0xc5, 0x43, 0xec, 0x13, 0x15, 0x88, 0x40, 0x37, - 0x7e, 0x09, 0x9f, 0xc2, 0xdc, 0x33, 0xcb, 0x3f, 0x57, 0x4f, 0x1e, 0x9a, 0xf3, 0xbf, 0xb0, 0xfc, - 0x73, 0x71, 0xf2, 0x48, 0xaa, 0x92, 0xd1, 0xf5, 0x7d, 0x58, 0xe2, 0xb3, 0x05, 0x71, 0xb5, 0xa8, - 0x32, 0xd4, 0xc2, 0xa3, 0x4c, 0x66, 0xa8, 0xb2, 0xc1, 0xd1, 0x40, 0x83, 0xea, 0x23, 0xa1, 0x87, - 0x1d, 0xc3, 0x49, 0x79, 0x22, 0xdd, 0x45, 0x91, 0x90, 0x14, 0x0d, 0x44, 0xfa, 0x09, 0x2c, 0x1c, - 0x77, 0xfb, 0x1d, 0xab, 0x47, 0xac, 0x5d, 0xc7, 0x5f, 0xa3, 0x3d, 0x80, 0x10, 0xc0, 0x67, 0x10, - 0xc9, 0x51, 0x38, 0xd0, 0xdc, 0xe5, 0x66, 0xa2, 0x10, 0x1a, 0x3b, 0x0c, 0x89, 0x4e, 0xff, 0xf3, - 0x49, 0x40, 0x7c, 0x0e, 0x72, 0x8d, 0xc3, 0x75, 0xec, 0x93, 0x30, 0xb5, 0x0e, 0xa9, 0xda, 0x1e, - 0xd7, 0xfa, 0xcc, 0x70, 0x50, 0x48, 0x59, 0x6d, 0x23, 0x55, 0xdb, 0x43, 0x1f, 0xc0, 0x34, 0x45, - 0xa3, 0xba, 0x5e, 0x0c, 0xe6, 0x93, 0x39, 0x94, 0x33, 0xc3, 0x41, 0x61, 0x9a, 0x5c, 0x17, 0xb1, - 0xc1, 0x90, 0xd1, 0x87, 0x90, 0xd9, 0xc3, 0x5d, 0xdc, 0x31, 0x7d, 0x5b, 0xf8, 0x0e, 0x0d, 0x47, - 0x6d, 0x01, 0x94, 0x4c, 0x14, 0x62, 0x92, 0x83, 0xc3, 0xc0, 0xa6, 0x67, 0xf7, 0xe4, 0x83, 0xc3, - 0xa5, 0x10, 0xf9, 0xe0, 0x60, 0x38, 0xe8, 0xaf, 0x35, 0x98, 0x2b, 0xf5, 0x7a, 0x36, 0xab, 0x02, - 0x78, 0xfc, 0xda, 0xb9, 0xb6, 0x1d, 0x54, 0x0d, 0x8e, 0xcc, 0x33, 0xdc, 0x6d, 0x92, 0xb3, 0xda, - 0x2b, 0x7f, 0x45, 0xa2, 0xce, 0x7f, 0x0c, 0x0a, 0x0f, 0x5e, 0xa7, 0x10, 0xb1, 0xdd, 0x70, 0x4d, - 0xcb, 0xf7, 0x68, 0x16, 0x1e, 0x4e, 0x28, 0xbb, 0x99, 0x24, 0x07, 0x7a, 0x07, 0xa6, 0xc9, 0x85, - 0x4c, 0x9c, 0x3f, 0xd4, 0xd8, 0xe4, 0xce, 0xa6, 0x64, 0x5d, 0x14, 0x43, 0xbf, 0x07, 0x19, 0xae, - 0xc9, 0xda, 0x5e, 0x92, 0x09, 0xf4, 0xc7, 0xf0, 0xb6, 0x61, 0x53, 0xed, 0x62, 0x0f, 0xfb, 0xc7, - 0xa6, 0xe7, 0xbd, 0xb0, 0xdd, 0x36, 0x4d, 0x4a, 0xb9, 0x4b, 0x0a, 0x6f, 0xbe, 0x07, 0xb3, 0x14, - 0x1c, 0xb0, 0xa1, 0x96, 0xa1, 0x49, 0xad, 0x21, 0x46, 0xf4, 0x0a, 0x6c, 0x1e, 0x62, 0x7f, 0x94, - 0xd7, 0x2b, 0x31, 0xf9, 0x95, 0x06, 0x85, 0x8a, 0x8b, 0x63, 0x85, 0xba, 0xd9, 0x56, 0xde, 0xe4, - 0x15, 0x98, 0x54, 0x38, 0x4a, 0x94, 0xce, 0xab, 0x2c, 0x3f, 0x84, 0xc9, 0x46, 0xe3, 0x88, 0xba, - 0xce, 0x24, 0xd5, 0xe0, 0xa4, 0xef, 0x77, 0xff, 0x7b, 0x50, 0x48, 0xef, 0xf5, 0x59, 0x85, 0xc6, - 0x20, 0xe3, 0xfa, 0x02, 0xcc, 0x1d, 0x5b, 0xbd, 0x0e, 0x9f, 0x51, 0xff, 0x8b, 0x14, 0xcc, 0xb3, - 0x6f, 0xcf, 0xb1, 0x7b, 0x2c, 0xca, 0xcb, 0x31, 0x49, 0xbb, 0x41, 0x4c, 0x42, 0x1f, 0xc3, 0x02, - 0xbf, 0xc0, 0x60, 0x97, 0x5e, 0x43, 0x98, 0x84, 0xf4, 0xea, 0xcd, 0xae, 0x30, 0xa7, 0x57, 0x6c, - 0xc4, 0x50, 0x11, 0xd1, 0x11, 0x2c, 0x32, 0xc0, 0x01, 0x36, 0xfd, 0x7e, 0x98, 0x53, 0x2d, 0xf1, - 0x43, 0x50, 0x80, 0x99, 0x4b, 0x70, 0x5e, 0xcf, 0x39, 0xd0, 0x88, 0xd0, 0xa2, 0x87, 0xb0, 0x74, - 0xec, 0xda, 0xdf, 0xbc, 0x94, 0xa2, 0x30, 0xdb, 0x15, 0x6b, 0x24, 0x05, 0x73, 0xc8, 0xd0, 0xa9, - 0x1c, 0x8b, 0xa3, 0xd8, 0xfa, 0xef, 0x53, 0x90, 0x0e, 0xb8, 0x6d, 0xcb, 0x47, 0x25, 0x8f, 0x73, - 0x34, 0xfa, 0x87, 0xf9, 0x8c, 0x21, 0x61, 0xa0, 0x5b, 0xf4, 0xf0, 0xe4, 0x11, 0x76, 0x96, 0x18, - 0xc0, 0x74, 0x1c, 0x83, 0xc0, 0x88, 0x9f, 0xee, 0x95, 0xe9, 0xd2, 0xd2, 0xcc, 0x4f, 0xdb, 0x67, - 0x46, 0x6a, 0xaf, 0x4c, 0x2c, 0xfa, 0xb4, 0xb6, 0x57, 0xa1, 0x52, 0xa6, 0x99, 0x45, 0x6d, 0xab, - 0xdd, 0x32, 0x28, 0x94, 0x8c, 0xd6, 0x4b, 0x8f, 0x8f, 0xe8, 0x2e, 0xe5, 0xa3, 0x9e, 0x79, 0xd9, - 0x35, 0x28, 0x14, 0x3d, 0x10, 0x11, 0xb4, 0x62, 0xf7, 0x7c, 0xd7, 0xee, 0x7a, 0xb4, 0xca, 0x93, - 0x56, 0x22, 0x65, 0x8b, 0x0f, 0x19, 0x11, 0x54, 0xf4, 0x0c, 0x36, 0x4a, 0xed, 0x2b, 0xb3, 0xd7, - 0xc2, 0x6d, 0x36, 0xf2, 0xcc, 0x76, 0x2f, 0x9e, 0x77, 0xed, 0x17, 0x1e, 0x4d, 0xdc, 0xd2, 0x3c, - 0x05, 0xe4, 0x28, 0xa7, 0x9c, 0xdd, 0x0b, 0x81, 0x64, 0x24, 0x51, 0xa3, 0x02, 0x4c, 0x57, 0xba, - 0x76, 0xbf, 0x4d, 0x33, 0xb6, 0x34, 0xdb, 0x08, 0x2d, 0x02, 0x30, 0x18, 0x5c, 0xff, 0x29, 0x2c, - 0x93, 0xe8, 0xe5, 0xe3, 0x1b, 0x1f, 0x61, 0xfa, 0x31, 0x40, 0x1d, 0x5f, 0x9a, 0xce, 0xb9, 0x4d, - 0xcc, 0x52, 0x96, 0xbf, 0x78, 0x4c, 0x47, 0x41, 0xb5, 0x85, 0x0f, 0x34, 0x77, 0xc5, 0x41, 0x2d, - 0x30, 0x0d, 0x89, 0x4a, 0xff, 0xa7, 0x14, 0xa0, 0x52, 0xbf, 0x6d, 0xf9, 0x75, 0xdf, 0xc5, 0xe6, - 0xa5, 0x10, 0xe3, 0x67, 0x30, 0xcf, 0x76, 0x28, 0x03, 0x53, 0x71, 0xc8, 0x81, 0xc1, 0x7c, 0x51, - 0x1e, 0xaa, 0x4e, 0x18, 0x0a, 0x2a, 0x21, 0x35, 0xb0, 0xd7, 0xbf, 0x14, 0xa4, 0x29, 0x85, 0x54, - 0x1e, 0x22, 0xa4, 0xf2, 0x37, 0x7a, 0x08, 0x8b, 0x15, 0xfb, 0xd2, 0x21, 0x3a, 0xe1, 0xc4, 0x93, - 0x3c, 0x2c, 0xf3, 0x79, 0x95, 0xc1, 0xea, 0x84, 0x11, 0x41, 0x47, 0x4f, 0x60, 0xe5, 0xa0, 0xdb, - 0xf7, 0xce, 0x4b, 0xbd, 0x76, 0xa5, 0x6b, 0x7b, 0x82, 0xcb, 0x14, 0xbf, 0x9d, 0xf0, 0x9d, 0x34, - 0x8a, 0x51, 0x9d, 0x30, 0xe2, 0x08, 0xd1, 0x0f, 0x79, 0xc9, 0x97, 0x1f, 0x0f, 0x0b, 0xdb, 0xbc, - 0x22, 0xfc, 0xb4, 0x87, 0x9f, 0x3e, 0xaf, 0x4e, 0x18, 0x6c, 0xb4, 0x9c, 0x81, 0x59, 0x11, 0x45, - 0x76, 0x60, 0x59, 0x52, 0x27, 0x39, 0xd0, 0xfa, 0x1e, 0xca, 0x43, 0xfa, 0xc4, 0x21, 0xd7, 0x5b, - 0x11, 0x16, 0x8d, 0xe0, 0x5b, 0x7f, 0x4f, 0xd5, 0x34, 0xda, 0x94, 0x13, 0x2d, 0x86, 0x1c, 0x02, - 0xf4, 0xaa, 0xaa, 0xdc, 0xf1, 0xd8, 0xca, 0xbc, 0xa9, 0xc8, 0xbc, 0xd9, 0xa8, 0xae, 0xf5, 0xb5, - 0x58, 0xe5, 0xe9, 0xbf, 0xd4, 0x60, 0xf5, 0x10, 0xfb, 0xb4, 0x50, 0x44, 0x62, 0x4c, 0x70, 0x60, - 0xfc, 0x58, 0xae, 0x19, 0x32, 0x7f, 0x5d, 0x18, 0x0e, 0x0a, 0x99, 0xa0, 0x42, 0x28, 0x57, 0x05, - 0x1f, 0xc0, 0x62, 0xfd, 0xc2, 0x72, 0x9a, 0x66, 0xd7, 0x6a, 0xd3, 0x18, 0xcc, 0xa3, 0x03, 0x8b, - 0x66, 0x17, 0x96, 0x73, 0x7a, 0x15, 0x0c, 0x19, 0x11, 0x54, 0xfd, 0x29, 0xac, 0x45, 0x24, 0xe0, - 0x21, 0xfa, 0x0f, 0x60, 0x96, 0x83, 0xb8, 0xfb, 0x8f, 0x14, 0x1b, 0xe7, 0x86, 0x83, 0xc2, 0xac, - 0xc7, 0xc9, 0x04, 0xb2, 0xfe, 0x18, 0xd6, 0x4f, 0x1c, 0x0f, 0xbb, 0x21, 0x4f, 0xb1, 0xa8, 0xdd, - 0xa0, 0x7a, 0xa9, 0xc5, 0x57, 0x2f, 0x61, 0x38, 0x28, 0xcc, 0x30, 0x86, 0xa2, 0x62, 0xa9, 0xb7, - 0x60, 0x9d, 0xed, 0xe4, 0x11, 0x76, 0xaf, 0xa4, 0x23, 0xb1, 0xf7, 0x53, 0xb1, 0x7b, 0xbf, 0x06, - 0x79, 0x3e, 0x49, 0xb7, 0xfb, 0xfd, 0x8c, 0xa1, 0xff, 0xa3, 0x06, 0x1b, 0x87, 0xb8, 0x87, 0x5d, - 0x93, 0x8a, 0xac, 0x1c, 0xbc, 0xf2, 0xf5, 0x5f, 0x1b, 0x7b, 0xfd, 0x2f, 0x88, 0x54, 0x25, 0x45, - 0x53, 0x15, 0x1a, 0xe0, 0x68, 0xaa, 0xc2, 0x13, 0x14, 0x72, 0x0c, 0x9c, 0x18, 0x35, 0x9e, 0xc2, - 0xd1, 0x63, 0xa0, 0xef, 0x5a, 0x06, 0x81, 0xa1, 0x5a, 0x58, 0x3a, 0x98, 0xba, 0xb6, 0x74, 0xb0, - 0xc2, 0x2f, 0x7d, 0xb3, 0xbc, 0x74, 0xa0, 0x14, 0x0c, 0xf4, 0x07, 0x90, 0x1b, 0x5d, 0x0b, 0xf7, - 0x8f, 0x02, 0x4c, 0xb3, 0xea, 0xdd, 0x48, 0x32, 0xc2, 0xe0, 0xfa, 0x5e, 0xe8, 0xdb, 0x74, 0xd3, - 0x48, 0x25, 0x93, 0xc8, 0xbe, 0x1a, 0x73, 0xdd, 0xd1, 0xeb, 0xa1, 0x7f, 0x72, 0x2e, 0x7c, 0xfe, - 0x4f, 0x88, 0x7f, 0xb2, 0x7a, 0xa4, 0x96, 0x5c, 0x8f, 0xe4, 0x3e, 0xca, 0x48, 0x05, 0x81, 0xfe, - 0x0c, 0xd6, 0x15, 0xa6, 0xa1, 0xd7, 0x7f, 0x06, 0x69, 0x01, 0x8b, 0x64, 0xf2, 0x0a, 0x5b, 0x6a, - 0x37, 0x4f, 0x10, 0x07, 0x24, 0xfa, 0x6f, 0x35, 0xd8, 0x60, 0x21, 0x67, 0x74, 0xdd, 0x37, 0xb7, - 0xfe, 0xff, 0xc5, 0x15, 0xef, 0x8b, 0xa9, 0x74, 0x2a, 0x3b, 0xa9, 0x37, 0x21, 0x37, 0x2a, 0xef, - 0x1b, 0xd0, 0xf0, 0x21, 0x6c, 0x48, 0xdb, 0xf6, 0x7b, 0xdb, 0x3f, 0x9c, 0xf1, 0x0d, 0xda, 0x3f, - 0x44, 0x7c, 0x63, 0xf6, 0xaf, 0xc1, 0x0a, 0x63, 0xac, 0xee, 0x95, 0xa2, 0xbc, 0x57, 0x62, 0x2b, - 0xdd, 0xa3, 0xdb, 0xe7, 0x31, 0xdd, 0x3e, 0x02, 0x25, 0x94, 0xf0, 0x43, 0x98, 0xe1, 0x4f, 0x85, - 0x4c, 0xbe, 0x18, 0x66, 0x34, 0x8e, 0xb2, 0xf7, 0x41, 0x83, 0x23, 0xeb, 0x39, 0xba, 0x64, 0x92, - 0x48, 0xf2, 0xca, 0x89, 0x08, 0x6f, 0xfa, 0x97, 0x24, 0x60, 0x45, 0x46, 0xbe, 0xe7, 0x19, 0xf0, - 0x14, 0x72, 0xec, 0x0c, 0x90, 0xb8, 0x7e, 0xaf, 0x53, 0xe0, 0x63, 0xc8, 0x31, 0x77, 0x8a, 0x61, - 0x38, 0x3e, 0xb4, 0x6f, 0xc1, 0x66, 0x10, 0xda, 0xe3, 0x56, 0xff, 0x6b, 0x0d, 0x6e, 0x1d, 0x62, - 0x5f, 0x7d, 0xef, 0xf8, 0x7f, 0x38, 0x87, 0xbf, 0x82, 0x7c, 0x9c, 0x18, 0xdc, 0x10, 0x9f, 0x47, - 0x0d, 0x91, 0xf8, 0xb4, 0x13, 0x6f, 0x90, 0x5f, 0xc0, 0x6d, 0x66, 0x10, 0x15, 0x5f, 0x2c, 0xf3, - 0x41, 0xc4, 0x26, 0x89, 0xdc, 0xe3, 0x6c, 0xf3, 0x97, 0x1a, 0xdc, 0x66, 0x2a, 0x8e, 0x67, 0xfe, - 0x4a, 0x3a, 0xbc, 0x07, 0x33, 0x55, 0x9b, 0xdc, 0xba, 0xb9, 0x39, 0xe9, 0x72, 0xce, 0x6d, 0xcf, - 0x27, 0x61, 0x81, 0x0f, 0x8d, 0x7f, 0xb3, 0xd0, 0x9f, 0x40, 0x21, 0xb0, 0xf8, 0x1b, 0x30, 0xab, - 0xde, 0x02, 0x24, 0xd8, 0x54, 0xea, 0x86, 0x60, 0x71, 0x0b, 0x26, 0x2b, 0x75, 0x83, 0x97, 0xfc, - 0xe9, 0x01, 0xdc, 0xf2, 0x5c, 0x83, 0xc0, 0xa2, 0xd1, 0x38, 0x75, 0x93, 0x82, 0xdb, 0x1f, 0xc3, - 0x8a, 0x32, 0x09, 0xb7, 0xfb, 0x26, 0x4c, 0x55, 0xb0, 0xeb, 0xf3, 0x69, 0xe8, 0x4a, 0x5b, 0xd8, - 0xf5, 0x0d, 0x0a, 0x45, 0x6f, 0xc3, 0x6c, 0xa5, 0x44, 0x0b, 0xc2, 0x34, 0x4f, 0x98, 0x67, 0x61, - 0xa9, 0x65, 0x9e, 0xb6, 0x68, 0x91, 0x58, 0x0c, 0xea, 0x7f, 0xa6, 0x49, 0xdc, 0x09, 0xf9, 0xf5, - 0x6b, 0xd8, 0x21, 0xf7, 0x1f, 0xa2, 0x33, 0x69, 0x09, 0xf4, 0x08, 0xe2, 0xb7, 0x63, 0xba, 0x02, - 0x09, 0xe5, 0xa6, 0x85, 0x81, 0xaf, 0x60, 0x55, 0x95, 0xe4, 0x8d, 0x2e, 0xf4, 0x07, 0xb4, 0x6c, - 0x49, 0x32, 0x24, 0xb1, 0x44, 0x24, 0xdf, 0xf9, 0xb8, 0x83, 0x7c, 0x04, 0x59, 0x8e, 0x15, 0x6e, - 0xb0, 0x7b, 0x22, 0xe1, 0x62, 0xdb, 0x4b, 0x7d, 0xde, 0x17, 0x55, 0xa1, 0x1f, 0x89, 0x5b, 0xe5, - 0x75, 0x33, 0x5c, 0x40, 0xee, 0xf1, 0x41, 0xa9, 0xd4, 0xf7, 0xcf, 0x71, 0xcf, 0xb7, 0x5a, 0xa6, - 0x8f, 0x2b, 0xe7, 0x66, 0xb7, 0x8b, 0x7b, 0x1d, 0xaa, 0xa8, 0x93, 0xe2, 0x41, 0x70, 0xb8, 0xf0, - 0xca, 0x7e, 0xf1, 0x20, 0xc0, 0x30, 0xc8, 0x38, 0xba, 0x0f, 0x53, 0x8d, 0xa7, 0x8d, 0x63, 0x7e, - 0xc5, 0x5b, 0xe5, 0x78, 0x04, 0x14, 0x22, 0x52, 0x0c, 0xfd, 0x1b, 0xd8, 0x88, 0x4c, 0x16, 0xac, - 0xea, 0x6d, 0x31, 0x97, 0x46, 0xaf, 0xaf, 0xc1, 0x5c, 0x02, 0xa1, 0x3a, 0xc1, 0x26, 0x7b, 0x47, - 0x99, 0x6c, 0x45, 0x9a, 0x4c, 0xc2, 0xa4, 0x28, 0xfc, 0x95, 0x93, 0xc2, 0xf4, 0x3f, 0x85, 0x79, - 0x59, 0x70, 0x72, 0x63, 0x7a, 0x84, 0x5f, 0x56, 0xcd, 0x5e, 0xbb, 0x2b, 0xf4, 0x11, 0x02, 0xc8, - 0x68, 0x80, 0xca, 0xaf, 0x4c, 0x21, 0x00, 0xad, 0xc2, 0x74, 0xc9, 0x71, 0x6a, 0x7b, 0x6c, 0x53, - 0x1b, 0xec, 0x03, 0xe5, 0x60, 0x56, 0x54, 0x7b, 0x68, 0x52, 0x63, 0x88, 0x4f, 0xdd, 0x82, 0x39, - 0x69, 0x21, 0xd7, 0x4c, 0xbd, 0x05, 0x50, 0xe9, 0x5a, 0xb8, 0x47, 0xc3, 0x1f, 0x9f, 0x5b, 0x82, - 0xd0, 0xab, 0x9e, 0xd5, 0xe9, 0xd1, 0x92, 0x0c, 0x17, 0x20, 0x04, 0xe8, 0x4b, 0xb0, 0xa0, 0xe8, - 0x5d, 0xd7, 0x61, 0x5e, 0xd6, 0x0d, 0x71, 0x81, 0x8a, 0xdd, 0x0e, 0x5c, 0x80, 0xfc, 0xad, 0xff, - 0x46, 0x83, 0xd5, 0xc7, 0x07, 0x25, 0x03, 0x77, 0x2c, 0xda, 0x7d, 0x12, 0x2c, 0x74, 0x47, 0xb6, - 0xc9, 0x6d, 0xd9, 0x26, 0x11, 0x4c, 0x61, 0x9c, 0xa2, 0x62, 0x9c, 0x4d, 0xc5, 0x38, 0xa3, 0x24, - 0xcc, 0x4a, 0xd2, 0xad, 0xf9, 0x57, 0x1a, 0xac, 0x48, 0x82, 0x04, 0x42, 0x6f, 0xcb, 0x72, 0xe4, - 0x47, 0xe5, 0x88, 0xfa, 0xc8, 0x4f, 0x15, 0x31, 0x6e, 0xc7, 0x88, 0x31, 0xd6, 0x57, 0xda, 0xb0, - 0x1a, 0xb7, 0x48, 0xd5, 0x2b, 0xb4, 0x44, 0xaf, 0x48, 0x25, 0x78, 0xc5, 0xa4, 0xea, 0x15, 0x26, - 0xac, 0xc4, 0x2c, 0x01, 0xbd, 0x0b, 0x59, 0x06, 0x63, 0xa1, 0x88, 0xd7, 0xe9, 0x09, 0xe5, 0x08, - 0xfc, 0x3a, 0x5f, 0xd1, 0x7f, 0xa7, 0xc1, 0x5a, 0xac, 0xf2, 0xd1, 0x3a, 0x39, 0x45, 0x5b, 0x2e, - 0xf6, 0x39, 0x6f, 0xfe, 0x45, 0xe0, 0x35, 0xcf, 0xeb, 0xf3, 0x8e, 0xb4, 0x8c, 0xc1, 0xbf, 0xd0, - 0x0f, 0x60, 0xe1, 0x18, 0xbb, 0x96, 0xdd, 0xae, 0xe3, 0x96, 0xdd, 0x6b, 0xb3, 0xaa, 0xe4, 0x82, - 0xa1, 0x02, 0x89, 0x82, 0x4a, 0xdd, 0x8e, 0xed, 0x5a, 0xfe, 0xf9, 0x25, 0xdf, 0x04, 0x21, 0x80, - 0xf0, 0xde, 0xb3, 0x3a, 0x96, 0xcf, 0xaa, 0xec, 0x0b, 0x06, 0xff, 0x22, 0x2a, 0x2a, 0xb5, 0x5a, - 0x76, 0xbf, 0xe7, 0xd3, 0x82, 0x5d, 0xc6, 0x10, 0x9f, 0xfa, 0xbb, 0xb0, 0x1a, 0x67, 0xb4, 0x58, - 0x27, 0xfe, 0x65, 0x0a, 0x56, 0x4a, 0xed, 0xf6, 0xe3, 0x83, 0xd2, 0x1e, 0x96, 0x53, 0xae, 0x0f, - 0x60, 0xaa, 0xd6, 0xb3, 0x7c, 0xee, 0x3c, 0x5b, 0xdc, 0x17, 0x62, 0x30, 0x09, 0x16, 0x71, 0x07, - 0xf2, 0x2f, 0x32, 0x60, 0x65, 0xff, 0x1b, 0xcb, 0xf3, 0xad, 0x5e, 0x87, 0x3a, 0x24, 0x9b, 0x98, - 0x3b, 0x94, 0x60, 0x92, 0x10, 0xca, 0xaa, 0x13, 0x46, 0x1c, 0x31, 0x6a, 0xc0, 0xfa, 0x13, 0xfc, - 0x22, 0xc6, 0xbf, 0x83, 0x67, 0xf3, 0x80, 0x6d, 0x8c, 0x9b, 0x26, 0xd0, 0xca, 0xdb, 0xe7, 0x37, - 0x29, 0x58, 0x55, 0x17, 0xc6, 0x67, 0x3e, 0x81, 0x55, 0x49, 0x20, 0xd5, 0x87, 0xe7, 0x8a, 0x85, - 0xf8, 0xe5, 0xc8, 0x3b, 0x35, 0x96, 0x1c, 0x3d, 0x83, 0x0d, 0x55, 0x28, 0x35, 0x66, 0x86, 0x3b, - 0x2f, 0x0e, 0xa5, 0x3a, 0x61, 0x24, 0x51, 0xa3, 0x22, 0x4c, 0x96, 0x5a, 0x17, 0x5c, 0x2d, 0xf1, - 0x26, 0x63, 0x2b, 0x2b, 0xb5, 0x2e, 0xc8, 0x9e, 0x2f, 0xb5, 0x2e, 0x94, 0x0d, 0xfc, 0x37, 0x1a, - 0x6c, 0x24, 0x58, 0x98, 0xec, 0x19, 0x06, 0x94, 0x4e, 0x42, 0x09, 0x82, 0x3e, 0x93, 0xde, 0x14, - 0x16, 0x8b, 0xef, 0x8c, 0xf7, 0x97, 0x6d, 0x06, 0x69, 0x04, 0x8f, 0x0e, 0x7a, 0x41, 0xb0, 0xa7, - 0x4f, 0x10, 0x69, 0x16, 0x88, 0xd8, 0x7b, 0xf3, 0x49, 0xf1, 0x20, 0xab, 0xe9, 0x95, 0xa8, 0x68, - 0xc1, 0x4a, 0xd0, 0x7d, 0x98, 0x61, 0x40, 0x6e, 0x18, 0xd1, 0x7a, 0x17, 0x22, 0xf3, 0x71, 0xfd, - 0xef, 0x34, 0x51, 0x6a, 0x1a, 0xf1, 0xf7, 0x8f, 0x14, 0x7f, 0x17, 0xcf, 0xf1, 0xf1, 0xc8, 0x8a, - 0xcb, 0x97, 0x61, 0xee, 0x75, 0x5c, 0x5d, 0x26, 0x92, 0x9d, 0xf1, 0xef, 0x35, 0x71, 0xad, 0x1e, - 0xf5, 0xc7, 0x7d, 0x98, 0x7f, 0x3d, 0x3f, 0x54, 0xc8, 0xd0, 0x87, 0xcc, 0x4d, 0x52, 0xe3, 0x57, - 0x3a, 0xd6, 0x53, 0x3e, 0x15, 0xd5, 0xb4, 0xd7, 0xf1, 0x15, 0x7d, 0x33, 0x86, 0x3a, 0x98, 0x4e, - 0x5f, 0xa7, 0xb7, 0xe2, 0x60, 0x28, 0xb8, 0xc6, 0x55, 0x68, 0x99, 0x40, 0x86, 0x07, 0xa1, 0x7f, - 0x96, 0x83, 0x82, 0x16, 0xac, 0xa8, 0x03, 0x08, 0x04, 0xe2, 0xe2, 0xb7, 0x4e, 0x3c, 0xec, 0xd6, - 0xad, 0x5e, 0xa7, 0x8b, 0x4f, 0x58, 0x8a, 0x1a, 0x5c, 0x1a, 0x7e, 0xa2, 0x38, 0xc1, 0x46, 0x42, - 0x4f, 0xc6, 0xff, 0x96, 0xe9, 0xff, 0x56, 0x83, 0x7c, 0x9c, 0x6c, 0x6f, 0xd6, 0xfa, 0xdb, 0x3c, - 0x0d, 0x67, 0xd2, 0xe6, 0x38, 0x79, 0x30, 0xa7, 0x58, 0x2c, 0x59, 0x24, 0xf9, 0x57, 0x31, 0xfb, - 0x6f, 0x35, 0x58, 0xad, 0x79, 0x54, 0xfc, 0xaf, 0xfb, 0x96, 0x8b, 0xdb, 0x42, 0x71, 0xdb, 0x71, - 0x9d, 0x3b, 0xd4, 0xf0, 0xd5, 0x89, 0xb8, 0xce, 0x9c, 0x0f, 0xa4, 0x86, 0x8b, 0xd4, 0xb8, 0x96, - 0x9c, 0xea, 0x44, 0xd8, 0x72, 0x81, 0xde, 0x86, 0xa9, 0x27, 0xe4, 0xfc, 0x9a, 0xe4, 0xdb, 0x9c, - 0x51, 0x10, 0xd0, 0x91, 0xdd, 0xb1, 0x7a, 0x44, 0x64, 0xf2, 0x51, 0x4e, 0xc3, 0x4c, 0xc3, 0x74, - 0x3b, 0xd8, 0xd7, 0x3f, 0x84, 0x4c, 0x30, 0x4c, 0xd3, 0x78, 0xe9, 0xf8, 0x23, 0x7f, 0x93, 0xec, - 0x83, 0x0e, 0x8a, 0xec, 0x83, 0x7e, 0xe8, 0xbb, 0xb0, 0x16, 0x59, 0x26, 0xb7, 0x41, 0x9e, 0x28, - 0x83, 0xc1, 0xd8, 0x43, 0x9e, 0x11, 0x7c, 0xeb, 0x15, 0x58, 0x1e, 0xd1, 0x22, 0x42, 0x52, 0x6f, - 0x1a, 0xd9, 0x48, 0xf5, 0x7a, 0x95, 0xc0, 0x82, 0x56, 0x34, 0x02, 0x6b, 0x1c, 0xd5, 0xcb, 0x33, - 0xcc, 0x2a, 0xef, 0xbe, 0x0b, 0x99, 0xa0, 0x13, 0x9e, 0x84, 0xc1, 0xda, 0x93, 0x5a, 0x83, 0x85, - 0xc1, 0xe3, 0x93, 0x46, 0x56, 0x43, 0x00, 0x33, 0x7b, 0xfb, 0x47, 0xfb, 0x8d, 0xfd, 0x6c, 0xaa, - 0xf8, 0x6f, 0x1f, 0xc0, 0x1c, 0xb1, 0x39, 0xaf, 0x77, 0xa0, 0x4f, 0x61, 0xb1, 0x8e, 0x7b, 0xed, - 0x47, 0x18, 0x3b, 0xa5, 0xae, 0x75, 0x85, 0x3d, 0x24, 0x36, 0x42, 0x00, 0xca, 0xaf, 0x8f, 0x14, - 0x8a, 0xf7, 0x2f, 0x1d, 0xff, 0xe5, 0x7d, 0x0d, 0xfd, 0x18, 0xe6, 0x68, 0xcb, 0x1f, 0x7d, 0x93, - 0xf1, 0xd0, 0xbc, 0xdc, 0x06, 0x98, 0x17, 0x5f, 0x74, 0xf0, 0x7d, 0x0d, 0x7d, 0x06, 0xb3, 0x87, - 0xd8, 0xa7, 0x1a, 0x7c, 0x2b, 0xd2, 0x7c, 0x5f, 0xeb, 0x05, 0xf7, 0x6a, 0xee, 0x1d, 0xf9, 0x68, - 0xfd, 0x07, 0x55, 0x20, 0xcd, 0xc9, 0x3d, 0xa4, 0x47, 0xe8, 0xbd, 0x18, 0x06, 0x2b, 0x11, 0x06, - 0x47, 0x96, 0xe7, 0x93, 0xfb, 0x2b, 0x2b, 0x78, 0x50, 0x31, 0xa2, 0x73, 0xe4, 0x47, 0xd6, 0x8e, - 0x0e, 0x49, 0x58, 0x22, 0x61, 0xe7, 0xa6, 0x72, 0x27, 0x28, 0x0b, 0x1d, 0xc1, 0x62, 0x50, 0x7e, - 0xb8, 0xf9, 0x22, 0x92, 0xb8, 0x7d, 0x02, 0xcb, 0xa2, 0x02, 0x1f, 0x44, 0x1a, 0x94, 0x14, 0x7b, - 0x02, 0x4b, 0x30, 0x34, 0x0c, 0x79, 0x99, 0x56, 0x8d, 0x1c, 0xe8, 0xae, 0xc4, 0x24, 0x36, 0xe0, - 0xe5, 0xdf, 0x1a, 0x83, 0xc1, 0x5c, 0xfe, 0xbe, 0xf6, 0xbe, 0x86, 0xbe, 0x80, 0x05, 0x65, 0x3f, - 0x20, 0x91, 0xa1, 0xc4, 0x05, 0x83, 0xfc, 0x66, 0xfc, 0x20, 0xdf, 0x42, 0x07, 0x64, 0xb9, 0x7e, - 0xa4, 0x8b, 0x27, 0x1f, 0xd7, 0xad, 0xc3, 0x1a, 0x43, 0xf3, 0xe2, 0xf9, 0x32, 0x42, 0xb2, 0x0f, - 0x2b, 0xbc, 0xac, 0xad, 0x34, 0x76, 0x27, 0xf4, 0xfd, 0x24, 0x6a, 0xff, 0x21, 0xac, 0x70, 0x5b, - 0x2a, 0x6c, 0xb2, 0xc1, 0x83, 0x2b, 0x6f, 0x11, 0x49, 0x64, 0xf0, 0x05, 0xac, 0xd5, 0x23, 0xeb, - 0x61, 0x8d, 0x38, 0xb7, 0x54, 0x16, 0x52, 0xc7, 0x4f, 0x22, 0xaf, 0x47, 0x80, 0xea, 0xfd, 0xb3, - 0x4b, 0x2b, 0x60, 0x77, 0x65, 0xe1, 0x17, 0xe8, 0x4e, 0x64, 0x49, 0x04, 0x48, 0xd1, 0x68, 0x45, - 0x3a, 0x9f, 0xb0, 0x62, 0xd4, 0x60, 0xcf, 0x2a, 0xec, 0xbd, 0xde, 0x74, 0xcc, 0x33, 0xab, 0x6b, - 0xf9, 0x16, 0x26, 0x6e, 0x21, 0x13, 0xc8, 0x43, 0xc2, 0x82, 0xb7, 0x12, 0x31, 0xd0, 0xe7, 0xb0, - 0x70, 0x88, 0xfd, 0xb0, 0xa9, 0x09, 0x6d, 0x8c, 0xb4, 0x41, 0x71, 0xbb, 0x89, 0x82, 0x86, 0xda, - 0x49, 0x55, 0x83, 0xec, 0x89, 0xd3, 0x36, 0x7d, 0x2c, 0xb1, 0xb8, 0x33, 0xc2, 0x82, 0xa3, 0x98, - 0xae, 0x79, 0xe9, 0x25, 0x6a, 0x6b, 0x07, 0xa6, 0x8e, 0xad, 0x5e, 0x07, 0x89, 0xaa, 0x87, 0xd4, - 0x8e, 0x92, 0x5f, 0x51, 0x60, 0xdc, 0xf5, 0x7c, 0x28, 0x5c, 0xd3, 0xcd, 0x83, 0x7e, 0x12, 0x1c, - 0x43, 0x37, 0xe9, 0xfa, 0xc9, 0x4b, 0xfb, 0x3e, 0x1e, 0xb1, 0xb9, 0x8b, 0x7e, 0x4e, 0xed, 0x30, - 0x8a, 0x81, 0xee, 0xf1, 0xb9, 0xc6, 0xb5, 0x04, 0xe5, 0x6f, 0x27, 0xce, 0xd0, 0xdc, 0x45, 0xa7, - 0xe2, 0x69, 0x27, 0x86, 0xfb, 0xdb, 0x4a, 0xb7, 0xc1, 0x6b, 0x4e, 0xb0, 0x43, 0xe3, 0x3c, 0xfd, - 0x71, 0xcc, 0x5a, 0x28, 0xad, 0xd4, 0x71, 0x91, 0x57, 0x7f, 0x5e, 0x83, 0x76, 0x69, 0x64, 0xa7, - 0x7d, 0x7e, 0x68, 0x5d, 0xa5, 0xf0, 0xe2, 0x49, 0xde, 0xd7, 0xd0, 0x2e, 0x00, 0x93, 0x92, 0x4e, - 0xa4, 0x0e, 0x27, 0x5a, 0x7f, 0x97, 0x84, 0xff, 0xf6, 0x2b, 0x12, 0x7d, 0x2e, 0x8e, 0x00, 0x4a, - 0x94, 0x53, 0x72, 0x5f, 0x79, 0x55, 0x49, 0xf4, 0x35, 0xc8, 0x96, 0x5a, 0x34, 0xa0, 0x05, 0x4d, - 0x20, 0x68, 0x2b, 0xd8, 0x2c, 0xea, 0x80, 0xe0, 0xb5, 0x16, 0xed, 0x29, 0x39, 0xc2, 0x24, 0x99, - 0xa9, 0xc2, 0x46, 0x70, 0x34, 0x45, 0x86, 0xe2, 0x29, 0x12, 0x85, 0xda, 0x87, 0xd5, 0x8a, 0xd9, - 0x6b, 0xe1, 0xee, 0xf7, 0x63, 0xf3, 0x09, 0xdd, 0xd9, 0x52, 0x83, 0xcc, 0x7a, 0x94, 0x9e, 0x6f, - 0x6c, 0xd1, 0x58, 0x2c, 0xa1, 0x96, 0x60, 0x89, 0x29, 0x31, 0x54, 0x4b, 0x12, 0x75, 0xd2, 0xf4, - 0x1f, 0xc1, 0xe2, 0x3e, 0x89, 0x7c, 0xfd, 0xb6, 0xe5, 0xb3, 0x1f, 0x0a, 0xaa, 0xfd, 0x22, 0x89, - 0x84, 0x55, 0x58, 0xe6, 0x07, 0x41, 0xd8, 0x39, 0x12, 0x04, 0xdf, 0xd1, 0xe6, 0x9c, 0xfc, 0xaa, - 0x60, 0x2b, 0x37, 0x99, 0x88, 0x63, 0x4e, 0x69, 0x94, 0x08, 0x8e, 0xb9, 0xb8, 0x06, 0x8e, 0xe0, - 0x98, 0x8b, 0xef, 0xad, 0x28, 0xc3, 0x52, 0xa4, 0x47, 0x02, 0xdd, 0x11, 0x87, 0x6d, 0x6c, 0xef, - 0x44, 0x4c, 0xc2, 0x52, 0x15, 0x5a, 0x1d, 0xe5, 0x11, 0xdf, 0x30, 0x91, 0xa8, 0xa3, 0xe3, 0xe0, - 0x94, 0x93, 0xbb, 0x1f, 0x90, 0x7a, 0xf9, 0x8b, 0xeb, 0x8c, 0x48, 0xe4, 0x58, 0x87, 0x6c, 0xb4, - 0x6f, 0x00, 0x6d, 0x05, 0x1a, 0x89, 0x6d, 0x8e, 0xc8, 0x17, 0x12, 0xc7, 0xb9, 0xd2, 0x24, 0x03, - 0xb0, 0x9f, 0x10, 0x45, 0x0d, 0x20, 0xbf, 0x32, 0x8f, 0x18, 0x40, 0x7d, 0x3c, 0x3e, 0xa4, 0x0f, - 0x05, 0x52, 0x03, 0x00, 0x4a, 0x58, 0x4a, 0xfe, 0x4e, 0x1c, 0x9f, 0xd0, 0x92, 0x75, 0xc8, 0x46, - 0xdf, 0xcf, 0x83, 0x95, 0x26, 0x34, 0x02, 0x04, 0x2b, 0x4d, 0x7c, 0x78, 0xff, 0x02, 0xb2, 0xd1, - 0xc7, 0xf3, 0x80, 0x69, 0xc2, 0xab, 0x7a, 0xa2, 0x29, 0x0e, 0x60, 0x55, 0x35, 0xe0, 0x35, 0xeb, - 0x4d, 0xce, 0x64, 0x16, 0x94, 0x27, 0x73, 0x24, 0x8e, 0x86, 0xc8, 0xeb, 0xfc, 0x88, 0xf6, 0x63, - 0x9e, 0xee, 0x99, 0xf6, 0xa5, 0xe7, 0xf7, 0x9b, 0x68, 0x3f, 0xee, 0xb5, 0x3e, 0x50, 0x94, 0x24, - 0x97, 0x88, 0xb8, 0xd1, 0x81, 0x57, 0x51, 0xd4, 0x4d, 0x44, 0x4b, 0xe2, 0xb3, 0x07, 0x73, 0xd2, - 0xbb, 0x3d, 0xba, 0xa5, 0xa8, 0x49, 0xf1, 0xf8, 0xbc, 0xb2, 0x38, 0xd5, 0xd9, 0x2b, 0x30, 0x2f, - 0xbf, 0xfe, 0x27, 0x4a, 0x71, 0x7b, 0x94, 0x87, 0x27, 0x65, 0xd3, 0x8b, 0x81, 0x16, 0x98, 0x34, - 0x9b, 0x51, 0xe5, 0x28, 0x02, 0x25, 0x2f, 0x09, 0xc9, 0xaa, 0xb9, 0x46, 0xa4, 0xe4, 0x93, 0x68, - 0x85, 0x9d, 0xc9, 0xea, 0x8f, 0x17, 0x13, 0x7e, 0x03, 0x39, 0x26, 0x5a, 0x2d, 0x45, 0xda, 0x15, - 0x90, 0xe4, 0x25, 0x31, 0x4f, 0xfc, 0xf9, 0xad, 0xa4, 0x61, 0xae, 0xa6, 0x23, 0x58, 0x1e, 0xe9, - 0x56, 0x40, 0x05, 0x25, 0x1e, 0x8f, 0xb6, 0x1d, 0x8c, 0xb9, 0xff, 0x2d, 0x8f, 0xb4, 0x2a, 0x04, - 0xdc, 0x92, 0x9a, 0x18, 0x12, 0xb9, 0x35, 0x60, 0x2d, 0xb6, 0x7d, 0x21, 0xc8, 0x0f, 0xc7, 0x35, - 0x37, 0x24, 0x72, 0xfd, 0x39, 0xa0, 0xd1, 0x66, 0x83, 0xe0, 0x46, 0x98, 0xd8, 0x0e, 0x11, 0xdc, - 0x08, 0xc7, 0x74, 0x2a, 0x1c, 0xc1, 0x6a, 0x5c, 0xa7, 0x01, 0xd2, 0x15, 0x7d, 0xc6, 0x76, 0x0a, - 0xc4, 0x1c, 0x72, 0x86, 0xd8, 0x94, 0x09, 0xdc, 0xc6, 0xf4, 0x1d, 0x24, 0x2e, 0xfe, 0x17, 0xa2, - 0x97, 0x64, 0xb4, 0x3f, 0x20, 0xc8, 0x8b, 0xaf, 0x69, 0x20, 0x18, 0x93, 0x6e, 0x2c, 0xd5, 0xad, - 0x4e, 0x4f, 0x7a, 0xca, 0x0f, 0x92, 0x8d, 0xd1, 0x1e, 0x82, 0x20, 0x00, 0xc4, 0xbd, 0xfc, 0x3f, - 0x85, 0x55, 0x71, 0x12, 0xca, 0x0f, 0xe6, 0x68, 0x84, 0x26, 0x7c, 0xcf, 0x0f, 0x82, 0x41, 0xec, - 0x0b, 0x3b, 0xcb, 0xd6, 0xe9, 0x2f, 0xd2, 0xa5, 0x6c, 0x5d, 0x7a, 0xc9, 0xce, 0xab, 0x8f, 0xde, - 0xe8, 0x01, 0xcd, 0xd6, 0x59, 0xbb, 0x61, 0xd2, 0x5e, 0xdf, 0x50, 0x39, 0x85, 0x6e, 0xb0, 0x2b, - 0xea, 0x2f, 0x74, 0x42, 0x95, 0xf3, 0xf5, 0x09, 0x38, 0x25, 0x52, 0x13, 0x70, 0x59, 0xd0, 0xe4, - 0x1b, 0xf2, 0xbc, 0xfc, 0x0c, 0x10, 0xe8, 0x2a, 0xe6, 0xa1, 0x21, 0xd0, 0x55, 0xdc, 0xbb, 0x01, - 0xcd, 0xf7, 0x1a, 0x22, 0xbf, 0x0a, 0xf9, 0xdd, 0x19, 0x5b, 0xf8, 0xcf, 0x6f, 0x8d, 0xaf, 0x96, - 0x4b, 0x59, 0x64, 0x58, 0xa7, 0x96, 0x93, 0x98, 0x91, 0xaa, 0xb6, 0x7c, 0x8c, 0xc6, 0x94, 0xb6, - 0x1f, 0xd3, 0x3e, 0x86, 0xa7, 0xb5, 0xbd, 0x0a, 0xff, 0x65, 0xbc, 0xed, 0x8e, 0x14, 0xae, 0xa4, - 0xdf, 0x47, 0x85, 0xda, 0x63, 0x28, 0x0a, 0x61, 0xb3, 0x88, 0xea, 0xb4, 0xf6, 0xa2, 0x40, 0x63, - 0x6a, 0x57, 0x31, 0x0c, 0xf3, 0xf1, 0x0c, 0x69, 0x1d, 0x8e, 0x06, 0x7d, 0xe2, 0x07, 0xaa, 0x98, - 0x09, 0x32, 0x8c, 0x3b, 0x3b, 0x98, 0x56, 0xe3, 0xd9, 0x08, 0xe9, 0xae, 0x73, 0x10, 0xa6, 0xb1, - 0x7a, 0xe9, 0xf1, 0xd1, 0x6b, 0x69, 0x4c, 0x21, 0x0c, 0x34, 0xa6, 0x40, 0x5f, 0x4d, 0x63, 0x11, - 0x86, 0xaa, 0xc6, 0x54, 0x31, 0x13, 0x64, 0xb8, 0x5e, 0x63, 0xf1, 0x6c, 0x6e, 0xaa, 0xb1, 0x2f, - 0xe9, 0x49, 0x71, 0x48, 0x7f, 0x9b, 0xf5, 0x4a, 0x3a, 0xcb, 0x89, 0xbc, 0x47, 0x25, 0x6d, 0xee, - 0xa2, 0x67, 0xb4, 0x47, 0x32, 0x02, 0xbf, 0x99, 0xde, 0x36, 0x93, 0x98, 0x52, 0xcd, 0xd5, 0x60, - 0x8d, 0x69, 0x2e, 0x2a, 0x6e, 0xa2, 0x2c, 0x89, 0xcb, 0x3e, 0x14, 0xc7, 0x6e, 0x94, 0xd5, 0xab, - 0xea, 0x6f, 0x8f, 0xba, 0x48, 0xc3, 0x25, 0x59, 0x4d, 0x7b, 0x34, 0xe5, 0x51, 0x99, 0x88, 0x6a, - 0x99, 0x8a, 0xde, 0x2c, 0xa2, 0x1a, 0xb5, 0x82, 0x0a, 0x1e, 0x97, 0x13, 0xc6, 0xb3, 0xa1, 0x4a, - 0xaa, 0x8a, 0xf3, 0x39, 0x22, 0x53, 0xd2, 0xdc, 0xc9, 0x42, 0x05, 0x09, 0xf3, 0x0d, 0x57, 0x97, - 0xa4, 0x22, 0x76, 0xce, 0xb0, 0xfc, 0xf4, 0x3a, 0xcd, 0x44, 0xff, 0x9f, 0x35, 0xe8, 0x0f, 0x21, - 0x23, 0x88, 0xaf, 0x57, 0x48, 0x94, 0x9a, 0x2a, 0xe4, 0x73, 0x98, 0xe3, 0x0a, 0xa1, 0x12, 0x24, - 0xcd, 0x94, 0x28, 0xfe, 0x67, 0x30, 0xc7, 0xd5, 0x30, 0x76, 0x05, 0x49, 0xe4, 0x4d, 0xda, 0xf7, - 0x99, 0xf0, 0xff, 0x94, 0x48, 0x5c, 0xd1, 0xb5, 0xff, 0x47, 0x0b, 0xc2, 0xb7, 0x9e, 0xcc, 0xf7, - 0x5a, 0xfa, 0x24, 0x79, 0xcb, 0xd9, 0x6f, 0xff, 0x6b, 0x4b, 0xfb, 0xf6, 0xbb, 0x2d, 0xed, 0x5f, - 0xbf, 0xdb, 0xd2, 0x7e, 0xff, 0xdd, 0x96, 0x76, 0x36, 0x43, 0x31, 0x76, 0xff, 0x27, 0x00, 0x00, - 0xff, 0xff, 0x65, 0x04, 0xd3, 0x8f, 0xf2, 0x4a, 0x00, 0x00, + // 5476 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x7c, 0x4b, 0x73, 0x23, 0xc9, + 0x71, 0x30, 0x1b, 0x7c, 0x01, 0xc9, 0x17, 0x58, 0x7c, 0x61, 0x30, 0x1c, 0x62, 0xd4, 0x23, 0xad, + 0x46, 0xab, 0x15, 0xb9, 0x02, 0x77, 0xbf, 0x5d, 0xed, 0xec, 0xee, 0x7c, 0x00, 0xf8, 0x00, 0x76, + 0x38, 0x33, 0xdc, 0x06, 0x88, 0xb1, 0xe4, 0x8d, 0xa0, 0x9b, 0x40, 0x0d, 0xd8, 0x41, 0x10, 0xdd, + 0xdb, 0xdd, 0xe0, 0xec, 0xf8, 0xa4, 0x83, 0xa4, 0x70, 0xd8, 0xe1, 0x08, 0x5f, 0x7c, 0xf0, 0xc9, + 0x8e, 0xf0, 0x5d, 0x27, 0x5f, 0x75, 0xdf, 0x70, 0x84, 0x2c, 0xdf, 0x1c, 0xe1, 0x03, 0x2c, 0xef, + 0x11, 0x3f, 0xc1, 0x27, 0x47, 0xbd, 0xba, 0xab, 0x1a, 0xdd, 0x20, 0xe7, 0x61, 0xfb, 0x32, 0xc3, + 0xce, 0xca, 0xcc, 0xca, 0xcc, 0xca, 0xca, 0xcc, 0xaa, 0x4a, 0x12, 0x96, 0xcd, 0xbe, 0x7f, 0xee, + 0x61, 0xf7, 0xca, 0x6a, 0xe1, 0x6d, 0xc7, 0xb5, 0x7d, 0x1b, 0x4d, 0xd3, 0xff, 0xf2, 0xab, 0x1d, + 0xbb, 0x63, 0xd3, 0x1f, 0x77, 0xc8, 0x4f, 0x6c, 0x30, 0x7f, 0xbb, 0x63, 0xdb, 0x9d, 0x2e, 0xde, + 0xa1, 0x5f, 0x67, 0xfd, 0xe7, 0x3b, 0xf8, 0xd2, 0xf1, 0x5f, 0xf2, 0xc1, 0x42, 0x74, 0xd0, 0xb7, + 0x2e, 0xb1, 0xe7, 0x9b, 0x97, 0x0e, 0x47, 0xf8, 0xa8, 0x63, 0xf9, 0xe7, 0xfd, 0xb3, 0xed, 0x96, + 0x7d, 0xb9, 0xd3, 0x71, 0xcd, 0x2b, 0xcb, 0x37, 0x7d, 0xcb, 0xee, 0x99, 0xdd, 0x1d, 0x1f, 0x77, + 0xb1, 0x63, 0xbb, 0xfe, 0x8e, 0xe9, 0x58, 0x3b, 0xfe, 0x4b, 0x07, 0x7b, 0xec, 0x5f, 0x4e, 0x58, + 0x79, 0x15, 0xc2, 0x17, 0xae, 0xe9, 0x38, 0xd8, 0x0d, 0x7f, 0xe0, 0x4c, 0x1e, 0xbe, 0x0a, 0x13, + 0x7c, 0x85, 0x7b, 0xbe, 0xf8, 0x8f, 0x31, 0xd0, 0xff, 0x65, 0x01, 0xa6, 0xf7, 0x09, 0x00, 0x7d, + 0x0c, 0x53, 0x8d, 0x97, 0x0e, 0xce, 0x69, 0x77, 0xb5, 0xfb, 0x8b, 0xc5, 0x2c, 0x1b, 0xdf, 0x7e, + 0xea, 0x60, 0x97, 0xb2, 0x2c, 0xa3, 0xe1, 0xa0, 0xb0, 0x48, 0x18, 0xbd, 0x67, 0x5f, 0x5a, 0x3e, + 0xb5, 0x91, 0x41, 0x29, 0xd0, 0x33, 0x58, 0x34, 0xb0, 0x67, 0xf7, 0xdd, 0x16, 0xae, 0x62, 0xb3, + 0x8d, 0xdd, 0x5c, 0xea, 0xae, 0x76, 0x7f, 0xae, 0xb8, 0xb6, 0xcd, 0xf4, 0x55, 0x07, 0xcb, 0xeb, + 0xc3, 0x41, 0x01, 0xb9, 0x1c, 0x16, 0x32, 0xab, 0x4e, 0x18, 0x11, 0x36, 0xe8, 0x2b, 0x58, 0xa8, + 0x60, 0xd7, 0x2f, 0xf5, 0xfd, 0x73, 0xdb, 0xb5, 0xfc, 0x97, 0xb9, 0x49, 0xca, 0x77, 0x9d, 0xf3, + 0x55, 0xc6, 0x9a, 0xc5, 0xf2, 0xe6, 0x70, 0x50, 0xc8, 0xb5, 0xb0, 0xeb, 0x9f, 0x9a, 0x02, 0xaa, + 0xb0, 0x57, 0x99, 0xa1, 0x3f, 0x81, 0xf9, 0x3a, 0x31, 0x57, 0xab, 0x61, 0x5f, 0xe0, 0x9e, 0x97, + 0x9b, 0x52, 0x84, 0x96, 0x87, 0x9a, 0xc5, 0xf2, 0xed, 0xe1, 0xa0, 0xb0, 0xe1, 0x51, 0xd8, 0xa9, + 0x4f, 0x81, 0x0a, 0x6b, 0x85, 0x13, 0xfa, 0x33, 0x58, 0x3c, 0x76, 0xed, 0x2b, 0xcb, 0xb3, 0xec, + 0x1e, 0x05, 0xe5, 0xa6, 0x29, 0xef, 0x0d, 0xce, 0x5b, 0x1d, 0x6c, 0x16, 0xcb, 0x77, 0x86, 0x83, + 0xc2, 0x2d, 0x47, 0x40, 0xd9, 0x04, 0xaa, 0x65, 0x54, 0x12, 0xd4, 0x80, 0xb9, 0x4a, 0xb7, 0xef, + 0xf9, 0xd8, 0x7d, 0x62, 0x5e, 0xe2, 0xdc, 0x0c, 0x65, 0xbf, 0x2a, 0xec, 0x12, 0x8e, 0x34, 0x8b, + 0xe5, 0xfc, 0x70, 0x50, 0x58, 0x6f, 0x31, 0xd0, 0x69, 0xcf, 0xbc, 0x54, 0x4d, 0x2e, 0xb3, 0xa1, + 0xf6, 0x66, 0x9f, 0x15, 0xbb, 0xf7, 0xdc, 0xea, 0xe4, 0x66, 0x55, 0x7b, 0xcb, 0x63, 0xcd, 0x5d, + 0x6e, 0x6f, 0xce, 0xb9, 0x45, 0xa1, 0x11, 0x7b, 0xcb, 0x04, 0xe8, 0x23, 0x98, 0x3a, 0xf1, 0xb0, + 0x9b, 0x4b, 0x53, 0xa6, 0x0b, 0x9c, 0x29, 0x01, 0x35, 0x8b, 0xcc, 0xbb, 0xfa, 0x1e, 0x76, 0x15, + 0x0e, 0x94, 0x80, 0x10, 0x1a, 0x76, 0x17, 0xe7, 0x32, 0x0a, 0x21, 0x01, 0x35, 0x77, 0x19, 0xa1, + 0x6b, 0x77, 0x55, 0xb5, 0x28, 0x01, 0xaa, 0x41, 0x86, 0xe8, 0xe5, 0x39, 0x66, 0x0b, 0xe7, 0x80, + 0x52, 0x67, 0x39, 0x75, 0x00, 0x2f, 0x6f, 0x0c, 0x07, 0x85, 0x95, 0x9e, 0xf8, 0x54, 0xb8, 0x84, + 0xd4, 0xe8, 0x21, 0xcc, 0xd4, 0xb1, 0x7b, 0x85, 0xdd, 0xdc, 0x1c, 0xe5, 0xb3, 0x24, 0xdc, 0x84, + 0x02, 0x9b, 0xc5, 0xf2, 0xea, 0x70, 0x50, 0xc8, 0x7a, 0xf4, 0x4b, 0xe1, 0xc1, 0xc9, 0x88, 0x6d, + 0x0d, 0x7c, 0x85, 0x5d, 0x0f, 0x37, 0xfa, 0xbd, 0x1e, 0xee, 0xe6, 0xe6, 0x15, 0xdb, 0x2a, 0x63, + 0xc2, 0x97, 0x5d, 0x06, 0x3c, 0xf5, 0x29, 0x54, 0xb5, 0xad, 0x42, 0x80, 0xce, 0x21, 0xcb, 0x7e, + 0xaa, 0xd8, 0xbd, 0x1e, 0x6e, 0x91, 0x0d, 0x9b, 0x5b, 0xa0, 0x13, 0xdc, 0xe2, 0x13, 0x44, 0x87, + 0x9b, 0xc5, 0x72, 0x61, 0x38, 0x28, 0xdc, 0x66, 0xbc, 0xc9, 0xf2, 0xf1, 0x01, 0x65, 0x9a, 0x11, + 0xae, 0x44, 0x8f, 0x52, 0xab, 0x85, 0x3d, 0xcf, 0xc0, 0x5f, 0xf7, 0xb1, 0xe7, 0xe7, 0x16, 0x15, + 0x3d, 0x94, 0x31, 0xe1, 0x23, 0x26, 0x05, 0x9e, 0xba, 0x0c, 0xaa, 0xea, 0xa1, 0x10, 0xa0, 0x63, + 0x80, 0x92, 0xe3, 0xd4, 0xb1, 0x47, 0x5c, 0x3d, 0xb7, 0x44, 0x59, 0xaf, 0x70, 0xd6, 0xcf, 0xf0, + 0x19, 0x1f, 0x68, 0x16, 0xcb, 0xb7, 0x86, 0x83, 0xc2, 0x9a, 0xe9, 0x38, 0xa7, 0x1e, 0x03, 0x29, + 0x4c, 0x25, 0x1e, 0xcc, 0xee, 0x97, 0xb6, 0x8f, 0xb9, 0x33, 0xe6, 0xb2, 0x11, 0xbb, 0x4b, 0x63, + 0x42, 0x5e, 0x97, 0x02, 0x4f, 0xb9, 0x6b, 0x47, 0xed, 0x2e, 0x11, 0x90, 0x9d, 0xbe, 0x67, 0xfa, + 0xe6, 0x99, 0xe9, 0x61, 0xee, 0x1e, 0xcb, 0xca, 0x4e, 0x57, 0x07, 0x9b, 0xbb, 0x6c, 0xa7, 0xb7, + 0x39, 0xf4, 0x34, 0xc6, 0x5f, 0x22, 0xfc, 0x88, 0x45, 0x42, 0xc5, 0x73, 0xe8, 0x1a, 0x8b, 0xbc, + 0xc0, 0x67, 0xf1, 0x16, 0x09, 0x51, 0x51, 0x15, 0xd2, 0xcf, 0xf0, 0x19, 0x8b, 0x4b, 0x2b, 0x94, + 0xdf, 0x72, 0xc8, 0x8f, 0x45, 0xa4, 0x5d, 0xb6, 0x2b, 0x08, 0xb7, 0xd1, 0x58, 0x14, 0x50, 0xa3, + 0x5f, 0x6b, 0xb0, 0x21, 0xe2, 0x07, 0xf6, 0x5f, 0xd8, 0xee, 0x85, 0xd5, 0xeb, 0xf0, 0xd0, 0xb1, + 0x4a, 0x39, 0xdf, 0x8d, 0x84, 0xa4, 0x08, 0x56, 0xb3, 0x58, 0xfe, 0xe1, 0x70, 0x50, 0xb8, 0x17, + 0x84, 0xa7, 0x60, 0x3c, 0x2e, 0x9e, 0x24, 0xcd, 0x55, 0x06, 0x48, 0x8b, 0xcc, 0xa1, 0x57, 0x61, + 0xfa, 0x99, 0xe9, 0xb7, 0xce, 0xd1, 0x43, 0x98, 0x7e, 0x64, 0xf5, 0xda, 0x5e, 0x4e, 0xbb, 0x3b, + 0x49, 0x37, 0x3e, 0x4b, 0x68, 0x74, 0x90, 0x0c, 0x94, 0x37, 0xbe, 0x1d, 0x14, 0x26, 0x86, 0x83, + 0xc2, 0xd2, 0x05, 0x41, 0x93, 0xb2, 0x1a, 0xa3, 0xd3, 0xff, 0x29, 0x05, 0x99, 0x00, 0x1b, 0x6d, + 0xc2, 0x14, 0xf9, 0x9f, 0xa6, 0xc7, 0x4c, 0x39, 0x3d, 0x1c, 0x14, 0xa6, 0x08, 0x9d, 0x41, 0xa1, + 0xa8, 0x08, 0x73, 0x47, 0xb6, 0xd9, 0xae, 0xe3, 0x96, 0x8b, 0x7d, 0x8f, 0xe6, 0xbf, 0x74, 0x39, + 0x3b, 0x1c, 0x14, 0xe6, 0xbb, 0xb6, 0xd9, 0x3e, 0xf5, 0x18, 0xdc, 0x90, 0x91, 0x08, 0x47, 0x1a, + 0xbc, 0x27, 0x43, 0x8e, 0x24, 0x0c, 0x19, 0x14, 0x8a, 0xbe, 0x80, 0x99, 0x03, 0xab, 0x4b, 0x1c, + 0x76, 0x8a, 0xca, 0xbf, 0x19, 0x95, 0x7f, 0x9b, 0x0d, 0xef, 0xf7, 0x7c, 0xf7, 0x25, 0x8b, 0x3e, + 0xcf, 0x29, 0x40, 0x52, 0x84, 0x73, 0x40, 0xef, 0xc3, 0x6c, 0xbd, 0x7f, 0x46, 0xc5, 0x9f, 0xa6, + 0x93, 0xd1, 0x14, 0xec, 0xf5, 0xcf, 0x4e, 0x89, 0x0a, 0x12, 0x81, 0x40, 0xcb, 0xff, 0x0c, 0xe6, + 0x24, 0xf6, 0x28, 0x0b, 0x93, 0x17, 0xf8, 0x25, 0xd3, 0xdd, 0x20, 0x3f, 0xa2, 0x55, 0x98, 0xbe, + 0x32, 0xbb, 0x7d, 0x4c, 0x55, 0xcd, 0x18, 0xec, 0xe3, 0x93, 0xd4, 0xc7, 0x9a, 0xfe, 0x25, 0x4c, + 0x93, 0x3c, 0xeb, 0xa1, 0x7b, 0x30, 0x59, 0xaf, 0x57, 0x29, 0xd1, 0x7c, 0x79, 0x79, 0x38, 0x28, + 0x2c, 0x78, 0xde, 0xb9, 0x34, 0x19, 0x19, 0x25, 0x48, 0x8d, 0xa3, 0x3a, 0xe5, 0xc2, 0x91, 0xfc, + 0xae, 0xbc, 0x16, 0x64, 0x54, 0xff, 0xc3, 0x0c, 0x64, 0x49, 0x26, 0xa0, 0x7c, 0x45, 0xa8, 0x78, + 0x0f, 0x32, 0xc7, 0xfd, 0xb3, 0xae, 0xd5, 0x7a, 0xc4, 0x25, 0x9b, 0x2f, 0x2f, 0x0e, 0x07, 0x05, + 0x70, 0x28, 0xf0, 0xf4, 0x02, 0xbf, 0x34, 0x42, 0x04, 0x74, 0x1f, 0xd2, 0x84, 0x03, 0x31, 0x30, + 0x13, 0xb9, 0x3c, 0x3f, 0x1c, 0x14, 0xd2, 0x7d, 0x0e, 0x33, 0x82, 0x51, 0x54, 0x87, 0xd9, 0xfd, + 0x6f, 0x1c, 0xcb, 0xc5, 0x1e, 0x2f, 0x37, 0xf2, 0xdb, 0xac, 0x06, 0xdc, 0x16, 0x35, 0xe0, 0x76, + 0x43, 0xd4, 0x80, 0xe5, 0x3b, 0xdc, 0x87, 0x96, 0x31, 0x23, 0x09, 0x25, 0xff, 0x9b, 0xff, 0x28, + 0x68, 0x86, 0xe0, 0x84, 0xde, 0x83, 0x99, 0x03, 0xdb, 0xbd, 0x34, 0x7d, 0x5a, 0x65, 0x64, 0xf8, + 0x7a, 0x51, 0x88, 0xb2, 0x5e, 0x14, 0x82, 0x0e, 0x60, 0xd1, 0xb0, 0xfb, 0x3e, 0x6e, 0xd8, 0x22, + 0x68, 0xb1, 0x65, 0xdb, 0x1a, 0x0e, 0x0a, 0x79, 0x97, 0x8c, 0x9c, 0xfa, 0xf6, 0x68, 0x78, 0x32, + 0x22, 0x54, 0x68, 0x1f, 0x16, 0x95, 0xf0, 0xea, 0xe5, 0x66, 0xee, 0x4e, 0xde, 0xcf, 0xb0, 0x20, + 0xa4, 0x06, 0x65, 0xd9, 0xe6, 0x11, 0x22, 0xf4, 0x04, 0x96, 0x1f, 0xf5, 0xcf, 0xb0, 0xdb, 0xc3, + 0x3e, 0xf6, 0x84, 0x44, 0xb3, 0x54, 0xa2, 0xbb, 0xc3, 0x41, 0x61, 0xf3, 0x22, 0x18, 0x8c, 0x91, + 0x69, 0x94, 0x14, 0x61, 0x58, 0xe2, 0x82, 0x8a, 0x58, 0xc7, 0x6b, 0x82, 0x75, 0xee, 0xe3, 0x91, + 0xd1, 0xf2, 0x3d, 0x6e, 0xe5, 0xdb, 0x81, 0xee, 0x22, 0x82, 0x4a, 0x13, 0x45, 0x79, 0xa2, 0x5d, + 0x48, 0x3f, 0xb1, 0xdb, 0x98, 0xee, 0xb1, 0x0c, 0x95, 0x96, 0xa5, 0x7a, 0xbb, 0x8d, 0x23, 0x75, + 0x90, 0x11, 0x20, 0xa2, 0x23, 0x98, 0x3e, 0xf1, 0xcc, 0x0e, 0x2b, 0x17, 0x16, 0x8b, 0xdf, 0xe3, + 0x12, 0x45, 0xbd, 0x8f, 0xd6, 0x9e, 0x14, 0xb1, 0xbc, 0x42, 0x42, 0x48, 0x9f, 0xfc, 0x28, 0x87, + 0x10, 0x3a, 0x86, 0xbe, 0x04, 0xe0, 0x52, 0x95, 0x1c, 0x87, 0x57, 0x0e, 0xcb, 0xaa, 0x92, 0x25, + 0xc7, 0x29, 0x6f, 0x71, 0xfd, 0xd6, 0x03, 0xfd, 0x4c, 0xc7, 0x91, 0xb8, 0x49, 0x4c, 0xf4, 0x3d, + 0xc8, 0x04, 0x73, 0xa3, 0x59, 0x98, 0x2c, 0x75, 0xbb, 0xd9, 0x09, 0xf2, 0x43, 0xbd, 0x5e, 0xcd, + 0x6a, 0x68, 0x11, 0x20, 0x34, 0x78, 0x36, 0x85, 0xe6, 0x21, 0x2d, 0x0c, 0x92, 0x9d, 0xa4, 0xf8, + 0x8e, 0x93, 0x9d, 0xd2, 0xff, 0x4d, 0x1b, 0x59, 0x03, 0x12, 0xc3, 0xea, 0xec, 0xd4, 0x44, 0x4d, + 0xc6, 0x02, 0x1d, 0x8d, 0x61, 0xfc, 0x30, 0x45, 0xad, 0x66, 0xc8, 0x48, 0x64, 0x5b, 0x1d, 0x13, + 0x6d, 0x5a, 0x76, 0x57, 0xde, 0x56, 0x0e, 0x87, 0x19, 0xc1, 0x28, 0x2a, 0x4a, 0x1b, 0x70, 0x32, + 0x0c, 0x42, 0x62, 0x03, 0xca, 0x8b, 0x11, 0x6c, 0xc5, 0x62, 0x28, 0x3c, 0xdf, 0x37, 0x94, 0x26, + 0x66, 0xf1, 0x03, 0x3c, 0xfd, 0x77, 0x9a, 0x6c, 0xf3, 0x20, 0xc8, 0x6a, 0xb1, 0x41, 0xf6, 0x3d, + 0xc8, 0xf0, 0xac, 0x58, 0xdb, 0xe3, 0xf2, 0xd3, 0x18, 0xc2, 0x53, 0xe8, 0xa9, 0xd5, 0x36, 0x42, + 0x04, 0xb4, 0x03, 0xc0, 0x02, 0x4a, 0xa9, 0xdd, 0x76, 0xb9, 0x12, 0x4b, 0xc3, 0x41, 0x61, 0x8e, + 0x87, 0x1c, 0xb3, 0xdd, 0x76, 0x0d, 0x09, 0x85, 0x58, 0x54, 0xae, 0xd2, 0xa7, 0x42, 0x8b, 0xca, + 0xf5, 0xb8, 0x52, 0x83, 0xeb, 0x5d, 0x58, 0x3c, 0xc4, 0x3e, 0x31, 0x81, 0x08, 0x74, 0xe3, 0x55, + 0xf8, 0x14, 0xe6, 0x9e, 0x59, 0xfe, 0xb9, 0x9a, 0x79, 0x68, 0xcd, 0xff, 0xc2, 0xf2, 0xcf, 0x45, + 0xe6, 0x91, 0x4c, 0x25, 0xa3, 0xeb, 0xfb, 0xb0, 0xc4, 0x67, 0x0b, 0xe2, 0x6a, 0x51, 0x65, 0xa8, + 0x85, 0xa9, 0x4c, 0x66, 0xa8, 0xb2, 0xc1, 0xd1, 0x40, 0x83, 0xea, 0x23, 0xa1, 0x87, 0xa5, 0xe1, + 0xa4, 0x3a, 0x91, 0xee, 0xa2, 0x48, 0x48, 0x8a, 0x06, 0x22, 0xfd, 0x04, 0x16, 0x8e, 0xbb, 0xfd, + 0x8e, 0xd5, 0x23, 0xab, 0x5d, 0xc7, 0x5f, 0xa3, 0x3d, 0x80, 0x10, 0xc0, 0x67, 0x10, 0xc5, 0x51, + 0x38, 0xd0, 0xdc, 0xe5, 0xcb, 0x44, 0x21, 0x34, 0x76, 0x18, 0x12, 0x9d, 0xfe, 0x97, 0x93, 0x80, + 0xf8, 0x1c, 0xe4, 0x18, 0x87, 0xeb, 0xd8, 0x27, 0x61, 0x6a, 0x1d, 0x52, 0xb5, 0x3d, 0x6e, 0xf5, + 0x99, 0xe1, 0xa0, 0x90, 0xb2, 0xda, 0x46, 0xaa, 0xb6, 0x87, 0x3e, 0x80, 0x69, 0x8a, 0x46, 0x6d, + 0xbd, 0x18, 0xcc, 0x27, 0x73, 0x28, 0x67, 0x86, 0x83, 0xc2, 0x34, 0x39, 0x2e, 0x62, 0x83, 0x21, + 0xa3, 0x0f, 0x21, 0xb3, 0x87, 0xbb, 0xb8, 0x63, 0xfa, 0xb6, 0xf0, 0x1d, 0x1a, 0x8e, 0xda, 0x02, + 0x28, 0x2d, 0x51, 0x88, 0x49, 0x12, 0x87, 0x81, 0x4d, 0xcf, 0xee, 0xc9, 0x89, 0xc3, 0xa5, 0x10, + 0x39, 0x71, 0x30, 0x1c, 0xf4, 0xb7, 0x1a, 0xcc, 0x95, 0x7a, 0x3d, 0x9b, 0xdd, 0x02, 0x78, 0xfc, + 0xd8, 0xb9, 0xb6, 0x1d, 0xdc, 0x1a, 0x1c, 0x99, 0x67, 0xb8, 0xdb, 0x24, 0xb9, 0xda, 0x2b, 0x7f, + 0x45, 0xa2, 0xce, 0xbf, 0x0f, 0x0a, 0x0f, 0x5e, 0xe7, 0x22, 0x62, 0xbb, 0xe1, 0x9a, 0x96, 0xef, + 0xd1, 0x2a, 0x3c, 0x9c, 0x50, 0x76, 0x33, 0x49, 0x0e, 0xf4, 0x23, 0x98, 0x26, 0x07, 0x32, 0x91, + 0x7f, 0xe8, 0x62, 0x93, 0x33, 0x9b, 0x52, 0x75, 0x51, 0x0c, 0xfd, 0x1e, 0x64, 0xb8, 0x25, 0x6b, + 0x7b, 0x49, 0x4b, 0xa0, 0x3f, 0x86, 0x77, 0x0c, 0x9b, 0x5a, 0x17, 0x7b, 0xd8, 0x3f, 0x36, 0x3d, + 0xef, 0x85, 0xed, 0xb6, 0x69, 0x51, 0xca, 0x5d, 0x52, 0x78, 0xf3, 0x3d, 0x98, 0xa5, 0xe0, 0x80, + 0x0d, 0x5d, 0x19, 0x5a, 0xd4, 0x1a, 0x62, 0x44, 0xaf, 0xc0, 0xe6, 0x21, 0xf6, 0x47, 0x79, 0xbd, + 0x12, 0x93, 0x5f, 0x69, 0x50, 0xa8, 0xb8, 0x38, 0x56, 0xa8, 0x9b, 0x6d, 0xe5, 0x4d, 0x7e, 0x03, + 0x93, 0x0a, 0x47, 0x89, 0xd1, 0xf9, 0x2d, 0xcb, 0x0f, 0x60, 0xb2, 0xd1, 0x38, 0xa2, 0xae, 0x33, + 0x49, 0x2d, 0x38, 0xe9, 0xfb, 0xdd, 0xff, 0x1a, 0x14, 0xd2, 0x7b, 0x7d, 0x76, 0x43, 0x63, 0x90, + 0x71, 0x7d, 0x01, 0xe6, 0x8e, 0xad, 0x5e, 0x87, 0xcf, 0xa8, 0xff, 0x55, 0x0a, 0xe6, 0xd9, 0xb7, + 0xe7, 0xd8, 0x3d, 0x16, 0xe5, 0xe5, 0x98, 0xa4, 0xdd, 0x20, 0x26, 0xa1, 0x8f, 0x61, 0x81, 0x1f, + 0x60, 0xb0, 0x4b, 0x8f, 0x21, 0x4c, 0x42, 0x7a, 0xf4, 0x66, 0x47, 0x98, 0xd3, 0x2b, 0x36, 0x62, + 0xa8, 0x88, 0xe8, 0x08, 0x16, 0x19, 0xe0, 0x00, 0x9b, 0x7e, 0x3f, 0xac, 0xa9, 0x96, 0x78, 0x12, + 0x14, 0x60, 0xe6, 0x12, 0x9c, 0xd7, 0x73, 0x0e, 0x34, 0x22, 0xb4, 0xe8, 0x21, 0x2c, 0x1d, 0xbb, + 0xf6, 0x37, 0x2f, 0xa5, 0x28, 0xcc, 0x76, 0xc5, 0x1a, 0x29, 0xc1, 0x1c, 0x32, 0x74, 0x2a, 0xc7, + 0xe2, 0x28, 0xb6, 0xfe, 0xc7, 0x14, 0xa4, 0x03, 0x6e, 0xdb, 0x72, 0xaa, 0xe4, 0x71, 0x8e, 0x46, + 0xff, 0xb0, 0x9e, 0x31, 0x24, 0x0c, 0x74, 0x8b, 0x26, 0x4f, 0x1e, 0x61, 0x67, 0xc9, 0x02, 0x98, + 0x8e, 0x63, 0x10, 0x18, 0xf1, 0xd3, 0xbd, 0x32, 0x55, 0x2d, 0xcd, 0xfc, 0xb4, 0x7d, 0x66, 0xa4, + 0xf6, 0xca, 0x64, 0x45, 0x9f, 0xd6, 0xf6, 0x2a, 0x54, 0xca, 0x34, 0x5b, 0x51, 0xdb, 0x6a, 0xb7, + 0x0c, 0x0a, 0x25, 0xa3, 0xf5, 0xd2, 0xe3, 0x23, 0xba, 0x4b, 0xf9, 0xa8, 0x67, 0x5e, 0x76, 0x0d, + 0x0a, 0x45, 0x0f, 0x44, 0x04, 0xad, 0xd8, 0x3d, 0xdf, 0xb5, 0xbb, 0x1e, 0xbd, 0xe5, 0x49, 0x2b, + 0x91, 0xb2, 0xc5, 0x87, 0x8c, 0x08, 0x2a, 0x7a, 0x06, 0x1b, 0xa5, 0xf6, 0x95, 0xd9, 0x6b, 0xe1, + 0x36, 0x1b, 0x79, 0x66, 0xbb, 0x17, 0xcf, 0xbb, 0xf6, 0x0b, 0x8f, 0x16, 0x6e, 0x69, 0x5e, 0x02, + 0x72, 0x94, 0x53, 0xce, 0xee, 0x85, 0x40, 0x32, 0x92, 0xa8, 0x51, 0x01, 0xa6, 0x2b, 0x5d, 0xbb, + 0xdf, 0xa6, 0x15, 0x5b, 0x9a, 0x6d, 0x84, 0x16, 0x01, 0x18, 0x0c, 0xae, 0xff, 0x14, 0x96, 0x49, + 0xf4, 0xf2, 0xf1, 0x8d, 0x53, 0x98, 0x7e, 0x0c, 0x50, 0xc7, 0x97, 0xa6, 0x73, 0x6e, 0x93, 0x65, + 0x29, 0xcb, 0x5f, 0x3c, 0xa6, 0xa3, 0xe0, 0xb6, 0x85, 0x0f, 0x34, 0x77, 0x45, 0xa2, 0x16, 0x98, + 0x86, 0x44, 0xa5, 0xff, 0x3e, 0x05, 0xa8, 0xd4, 0x6f, 0x5b, 0x7e, 0xdd, 0x77, 0xb1, 0x79, 0x29, + 0xc4, 0xf8, 0x19, 0xcc, 0xb3, 0x1d, 0xca, 0xc0, 0x54, 0x1c, 0x92, 0x30, 0x98, 0x2f, 0xca, 0x43, + 0xd5, 0x09, 0x43, 0x41, 0x25, 0xa4, 0x06, 0xf6, 0xfa, 0x97, 0x82, 0x34, 0xa5, 0x90, 0xca, 0x43, + 0x84, 0x54, 0xfe, 0x46, 0x0f, 0x61, 0xb1, 0x62, 0x5f, 0x3a, 0xc4, 0x26, 0x9c, 0x78, 0x92, 0x87, + 0x65, 0x3e, 0xaf, 0x32, 0x58, 0x9d, 0x30, 0x22, 0xe8, 0xe8, 0x09, 0xac, 0x1c, 0x74, 0xfb, 0xde, + 0x79, 0xa9, 0xd7, 0xae, 0x74, 0x6d, 0x4f, 0x70, 0x99, 0xe2, 0xa7, 0x13, 0xbe, 0x93, 0x46, 0x31, + 0xaa, 0x13, 0x46, 0x1c, 0x21, 0xfa, 0x01, 0xbf, 0xf2, 0xe5, 0xe9, 0x61, 0x61, 0x9b, 0xdf, 0x08, + 0x3f, 0xed, 0xe1, 0xa7, 0xcf, 0xab, 0x13, 0x06, 0x1b, 0x2d, 0x67, 0x60, 0x56, 0x44, 0x91, 0x1d, + 0x58, 0x96, 0xcc, 0x49, 0x12, 0x5a, 0xdf, 0x43, 0x79, 0x48, 0x9f, 0x38, 0xe4, 0x78, 0x2b, 0xc2, + 0xa2, 0x11, 0x7c, 0xeb, 0xef, 0xa9, 0x96, 0x46, 0x9b, 0x72, 0xa1, 0xc5, 0x90, 0x43, 0x80, 0x5e, + 0x55, 0x8d, 0x3b, 0x1e, 0x5b, 0x99, 0x37, 0x15, 0x99, 0x37, 0x1b, 0xb5, 0xb5, 0xbe, 0x16, 0x6b, + 0x3c, 0xfd, 0x97, 0x1a, 0xac, 0x1e, 0x62, 0x9f, 0x5e, 0x14, 0x91, 0x18, 0x13, 0x24, 0x8c, 0x1f, + 0xcb, 0x77, 0x86, 0xcc, 0x5f, 0x17, 0x86, 0x83, 0x42, 0x26, 0xb8, 0x21, 0x94, 0x6f, 0x05, 0x1f, + 0xc0, 0x62, 0xfd, 0xc2, 0x72, 0x9a, 0x66, 0xd7, 0x6a, 0xd3, 0x18, 0xcc, 0xa3, 0x03, 0x8b, 0x66, + 0x17, 0x96, 0x73, 0x7a, 0x15, 0x0c, 0x19, 0x11, 0x54, 0xfd, 0x29, 0xac, 0x45, 0x24, 0xe0, 0x21, + 0xfa, 0xff, 0xc1, 0x2c, 0x07, 0x71, 0xf7, 0x1f, 0xb9, 0x6c, 0x9c, 0x1b, 0x0e, 0x0a, 0xb3, 0x1e, + 0x27, 0x13, 0xc8, 0xfa, 0x63, 0x58, 0x3f, 0x71, 0x3c, 0xec, 0x86, 0x3c, 0x85, 0x52, 0xbb, 0xc1, + 0xed, 0xa5, 0x16, 0x7f, 0x7b, 0x09, 0xc3, 0x41, 0x61, 0x86, 0x31, 0x14, 0x37, 0x96, 0x7a, 0x0b, + 0xd6, 0xd9, 0x4e, 0x1e, 0x61, 0xf7, 0x4a, 0x36, 0x12, 0x7b, 0x3f, 0x15, 0xbb, 0xf7, 0x6b, 0x90, + 0xe7, 0x93, 0x74, 0xbb, 0x6f, 0xb6, 0x18, 0xfa, 0x3f, 0x6b, 0xb0, 0x71, 0x88, 0x7b, 0xd8, 0x35, + 0xa9, 0xc8, 0x4a, 0xe2, 0x95, 0x8f, 0xff, 0xda, 0xd8, 0xe3, 0x7f, 0x41, 0x94, 0x2a, 0x29, 0x5a, + 0xaa, 0xd0, 0x00, 0x47, 0x4b, 0x15, 0x5e, 0xa0, 0x90, 0x34, 0x70, 0x62, 0xd4, 0x78, 0x09, 0x47, + 0xd3, 0x40, 0xdf, 0xb5, 0x0c, 0x02, 0x43, 0xb5, 0xf0, 0xea, 0x60, 0xea, 0xda, 0xab, 0x83, 0x15, + 0x7e, 0xe8, 0x9b, 0xe5, 0x57, 0x07, 0xca, 0x85, 0x81, 0xfe, 0x00, 0x72, 0xa3, 0xba, 0x70, 0xff, + 0x28, 0xc0, 0x34, 0xbb, 0xbd, 0x1b, 0x29, 0x46, 0x18, 0x5c, 0xdf, 0x0b, 0x7d, 0x9b, 0x6e, 0x1a, + 0xe9, 0xca, 0x24, 0xb2, 0xaf, 0xc6, 0x1c, 0x77, 0xf4, 0x7a, 0xe8, 0x9f, 0x9c, 0x0b, 0x9f, 0xff, + 0x13, 0xe2, 0x9f, 0xec, 0x3e, 0x52, 0x4b, 0xbe, 0x8f, 0xe4, 0x3e, 0xca, 0x48, 0x05, 0x81, 0xfe, + 0x0c, 0xd6, 0x15, 0xa6, 0xa1, 0xd7, 0x7f, 0x06, 0x69, 0x01, 0x8b, 0x54, 0xf2, 0x0a, 0x5b, 0xba, + 0x6e, 0x9e, 0x20, 0x0e, 0x48, 0xf4, 0xdf, 0x6a, 0xb0, 0xc1, 0x42, 0xce, 0xa8, 0xde, 0x37, 0x5f, + 0xfd, 0xff, 0x8d, 0x23, 0xde, 0x17, 0x53, 0xe9, 0x54, 0x76, 0x52, 0x6f, 0x42, 0x6e, 0x54, 0xde, + 0xb7, 0x60, 0xe1, 0x43, 0xd8, 0x90, 0xb6, 0xed, 0x1b, 0xaf, 0x7f, 0x38, 0xe3, 0x5b, 0x5c, 0xff, + 0x10, 0xf1, 0xad, 0xad, 0x7f, 0x0d, 0x56, 0x18, 0x63, 0x75, 0xaf, 0x14, 0xe5, 0xbd, 0x12, 0x7b, + 0xd3, 0x3d, 0xba, 0x7d, 0x1e, 0xd3, 0xed, 0x23, 0x50, 0x42, 0x09, 0x3f, 0x84, 0x19, 0xfe, 0x54, + 0xc8, 0xe4, 0x8b, 0x61, 0x46, 0xe3, 0x28, 0x7b, 0x1f, 0x34, 0x38, 0xb2, 0x9e, 0xa3, 0x2a, 0x93, + 0x42, 0x92, 0xdf, 0x9c, 0x88, 0xf0, 0xa6, 0x7f, 0x49, 0x02, 0x56, 0x64, 0xe4, 0x0d, 0x73, 0xc0, + 0x53, 0xc8, 0xb1, 0x1c, 0x20, 0x71, 0x7d, 0xa3, 0x2c, 0xf0, 0x31, 0xe4, 0x98, 0x3b, 0xc5, 0x30, + 0x1c, 0x1f, 0xda, 0xb7, 0x60, 0x33, 0x08, 0xed, 0x71, 0xda, 0xff, 0x5a, 0x83, 0x5b, 0x87, 0xd8, + 0x57, 0xdf, 0x3b, 0xfe, 0x0f, 0xf2, 0xf0, 0x57, 0x90, 0x8f, 0x13, 0x83, 0x2f, 0xc4, 0xe7, 0xd1, + 0x85, 0x48, 0x7c, 0xda, 0x89, 0x5f, 0x90, 0x5f, 0xc0, 0x6d, 0xb6, 0x20, 0x2a, 0xbe, 0x50, 0xf3, + 0x41, 0x64, 0x4d, 0x12, 0xb9, 0xc7, 0xad, 0xcd, 0x5f, 0x6b, 0x70, 0x9b, 0x99, 0x38, 0x9e, 0xf9, + 0x2b, 0xd9, 0xf0, 0x1e, 0xcc, 0x54, 0x6d, 0x72, 0xea, 0xe6, 0xcb, 0x49, 0xd5, 0x39, 0xb7, 0x3d, + 0x9f, 0x84, 0x05, 0x3e, 0x34, 0xfe, 0xcd, 0x42, 0x7f, 0x02, 0x85, 0x60, 0xc5, 0xdf, 0xc2, 0xb2, + 0xea, 0x2d, 0x40, 0x82, 0x4d, 0xa5, 0x6e, 0x08, 0x16, 0xb7, 0x60, 0xb2, 0x52, 0x37, 0xf8, 0x95, + 0x3f, 0x4d, 0xc0, 0x2d, 0xcf, 0x35, 0x08, 0x2c, 0x1a, 0x8d, 0x53, 0x37, 0xb9, 0x70, 0xfb, 0x53, + 0x58, 0x51, 0x26, 0xe1, 0xeb, 0xbe, 0x09, 0x53, 0x15, 0xec, 0xfa, 0x7c, 0x1a, 0xaa, 0x69, 0x0b, + 0xbb, 0xbe, 0x41, 0xa1, 0xe8, 0x1d, 0x98, 0xad, 0x94, 0xe8, 0x85, 0x30, 0xad, 0x13, 0xe6, 0x59, + 0x58, 0x6a, 0x99, 0xa7, 0x2d, 0x7a, 0x49, 0x2c, 0x06, 0xf5, 0xbf, 0xd0, 0x24, 0xee, 0x84, 0xfc, + 0x7a, 0x1d, 0x76, 0xc8, 0xf9, 0x87, 0xd8, 0x4c, 0x52, 0x81, 0xa6, 0x20, 0x7e, 0x3a, 0xa6, 0x1a, + 0x48, 0x28, 0x37, 0xbd, 0x18, 0xf8, 0x0a, 0x56, 0x55, 0x49, 0xde, 0xaa, 0xa2, 0xdf, 0xa7, 0xd7, + 0x96, 0xa4, 0x42, 0x12, 0x2a, 0x22, 0xf9, 0xcc, 0xc7, 0x1d, 0xe4, 0x23, 0xc8, 0x72, 0xac, 0x70, + 0x83, 0xdd, 0x13, 0x05, 0x17, 0xdb, 0x5e, 0xea, 0xf3, 0xbe, 0xb8, 0x15, 0xfa, 0xa1, 0x38, 0x55, + 0x5e, 0x37, 0xc3, 0x05, 0xe4, 0x1e, 0x1f, 0x94, 0x4a, 0x7d, 0xff, 0x1c, 0xf7, 0x7c, 0xab, 0x65, + 0xfa, 0xb8, 0x72, 0x6e, 0x76, 0xbb, 0xb8, 0xd7, 0xa1, 0x86, 0x3a, 0x29, 0x1e, 0x04, 0xc9, 0x85, + 0xdf, 0xec, 0x17, 0x0f, 0x02, 0x0c, 0x83, 0x8c, 0xa3, 0xfb, 0x30, 0xd5, 0x78, 0xda, 0x38, 0xe6, + 0x47, 0xbc, 0x55, 0x8e, 0x47, 0x40, 0x21, 0x22, 0xc5, 0xd0, 0xbf, 0x81, 0x8d, 0xc8, 0x64, 0x81, + 0x56, 0xef, 0x88, 0xb9, 0x34, 0x7a, 0x7c, 0x0d, 0xe6, 0x12, 0x08, 0xd5, 0x09, 0x36, 0xd9, 0x8f, + 0x94, 0xc9, 0x56, 0xa4, 0xc9, 0x24, 0x4c, 0x8a, 0xc2, 0x5f, 0x39, 0x29, 0x4c, 0xff, 0x73, 0x98, + 0x97, 0x05, 0x27, 0x27, 0xa6, 0x47, 0xf8, 0x65, 0xd5, 0xec, 0xb5, 0xbb, 0xc2, 0x1e, 0x21, 0x80, + 0x8c, 0x06, 0xa8, 0xfc, 0xc8, 0x14, 0x02, 0xd0, 0x2a, 0x4c, 0x97, 0x1c, 0xa7, 0xb6, 0xc7, 0x36, + 0xb5, 0xc1, 0x3e, 0x50, 0x0e, 0x66, 0xc5, 0x6d, 0x0f, 0x2d, 0x6a, 0x0c, 0xf1, 0xa9, 0x5b, 0x30, + 0x27, 0x29, 0x72, 0xcd, 0xd4, 0x5b, 0x00, 0x95, 0xae, 0x85, 0x7b, 0x34, 0xfc, 0xf1, 0xb9, 0x25, + 0x08, 0x3d, 0xea, 0x59, 0x9d, 0x1e, 0xbd, 0x92, 0xe1, 0x02, 0x84, 0x00, 0x7d, 0x09, 0x16, 0x14, + 0xbb, 0xeb, 0x3a, 0xcc, 0xcb, 0xb6, 0x21, 0x2e, 0x50, 0xb1, 0xdb, 0x81, 0x0b, 0x90, 0x9f, 0xf5, + 0xdf, 0x68, 0xb0, 0xfa, 0xf8, 0xa0, 0x64, 0xe0, 0x8e, 0x45, 0xbb, 0x4f, 0x02, 0x45, 0x77, 0xe4, + 0x35, 0xb9, 0x2d, 0xaf, 0x49, 0x04, 0x53, 0x2c, 0x4e, 0x51, 0x59, 0x9c, 0x4d, 0x65, 0x71, 0x46, + 0x49, 0xd8, 0x2a, 0x49, 0xa7, 0xe6, 0x5f, 0x69, 0xb0, 0x22, 0x09, 0x12, 0x08, 0xbd, 0x2d, 0xcb, + 0x91, 0x1f, 0x95, 0x23, 0xea, 0x23, 0x3f, 0x55, 0xc4, 0xb8, 0x1d, 0x23, 0xc6, 0x58, 0x5f, 0x69, + 0xc3, 0x6a, 0x9c, 0x92, 0xaa, 0x57, 0x68, 0x89, 0x5e, 0x91, 0x4a, 0xf0, 0x8a, 0x49, 0xd5, 0x2b, + 0x4c, 0x58, 0x89, 0x51, 0x01, 0xbd, 0x0b, 0x59, 0x06, 0x63, 0xa1, 0x88, 0xdf, 0xd3, 0x13, 0xca, + 0x11, 0xf8, 0x75, 0xbe, 0xa2, 0xff, 0x4e, 0x83, 0xb5, 0x58, 0xe3, 0xa3, 0x75, 0x92, 0x45, 0x5b, + 0x2e, 0xf6, 0x39, 0x6f, 0xfe, 0x45, 0xe0, 0x35, 0xcf, 0xeb, 0xf3, 0x8e, 0xb4, 0x8c, 0xc1, 0xbf, + 0xd0, 0xf7, 0x61, 0xe1, 0x18, 0xbb, 0x96, 0xdd, 0xae, 0xe3, 0x96, 0xdd, 0x6b, 0xb3, 0x5b, 0xc9, + 0x05, 0x43, 0x05, 0x12, 0x03, 0x95, 0xba, 0x1d, 0xdb, 0xb5, 0xfc, 0xf3, 0x4b, 0xbe, 0x09, 0x42, + 0x00, 0xe1, 0xbd, 0x67, 0x75, 0x2c, 0x9f, 0xdd, 0xb2, 0x2f, 0x18, 0xfc, 0x8b, 0x98, 0xa8, 0xd4, + 0x6a, 0xd9, 0xfd, 0x9e, 0x4f, 0x2f, 0xec, 0x32, 0x86, 0xf8, 0xd4, 0xdf, 0x85, 0xd5, 0xb8, 0x45, + 0x8b, 0x75, 0xe2, 0x5f, 0xa6, 0x60, 0xa5, 0xd4, 0x6e, 0x3f, 0x3e, 0x28, 0xed, 0x61, 0xb9, 0xe4, + 0xfa, 0x00, 0xa6, 0x6a, 0x3d, 0xcb, 0xe7, 0xce, 0xb3, 0xc5, 0x7d, 0x21, 0x06, 0x93, 0x60, 0x11, + 0x77, 0x20, 0xff, 0x23, 0x03, 0x56, 0xf6, 0xbf, 0xb1, 0x3c, 0xdf, 0xea, 0x75, 0xa8, 0x43, 0xb2, + 0x89, 0xb9, 0x43, 0x09, 0x26, 0x09, 0xa1, 0xac, 0x3a, 0x61, 0xc4, 0x11, 0xa3, 0x06, 0xac, 0x3f, + 0xc1, 0x2f, 0x62, 0xfc, 0x3b, 0x78, 0x36, 0x0f, 0xd8, 0xc6, 0xb8, 0x69, 0x02, 0xad, 0xbc, 0x7d, + 0x7e, 0x93, 0x82, 0x55, 0x55, 0x31, 0x3e, 0xf3, 0x09, 0xac, 0x4a, 0x02, 0xa9, 0x3e, 0x3c, 0x57, + 0x2c, 0xc4, 0xab, 0x23, 0xef, 0xd4, 0x58, 0x72, 0xf4, 0x0c, 0x36, 0x54, 0xa1, 0xd4, 0x98, 0x19, + 0xee, 0xbc, 0x38, 0x94, 0xea, 0x84, 0x91, 0x44, 0x8d, 0x8a, 0x30, 0x59, 0x6a, 0x5d, 0x70, 0xb3, + 0xc4, 0x2f, 0x19, 0xd3, 0xac, 0xd4, 0xba, 0x20, 0x7b, 0xbe, 0xd4, 0xba, 0x50, 0x36, 0xf0, 0xdf, + 0x69, 0xb0, 0x91, 0xb0, 0xc2, 0x64, 0xcf, 0x30, 0xa0, 0x94, 0x09, 0x25, 0x08, 0xfa, 0x4c, 0x7a, + 0x53, 0x58, 0x2c, 0xfe, 0x68, 0xbc, 0xbf, 0x6c, 0x33, 0x48, 0x23, 0x78, 0x74, 0xd0, 0x0b, 0x82, + 0x3d, 0x7d, 0x82, 0x48, 0xb3, 0x40, 0xc4, 0xde, 0x9b, 0x4f, 0x8a, 0x07, 0x59, 0x4d, 0xaf, 0x44, + 0x45, 0x0b, 0x34, 0x41, 0xf7, 0x61, 0x86, 0x01, 0xf9, 0xc2, 0x88, 0xd6, 0xbb, 0x10, 0x99, 0x8f, + 0xeb, 0xff, 0xa0, 0x89, 0xab, 0xa6, 0x11, 0x7f, 0xff, 0x48, 0xf1, 0x77, 0xf1, 0x1c, 0x1f, 0x8f, + 0xac, 0xb8, 0x7c, 0x19, 0xe6, 0x5e, 0xc7, 0xd5, 0x65, 0x22, 0xd9, 0x19, 0xff, 0x51, 0x13, 0xc7, + 0xea, 0x51, 0x7f, 0xdc, 0x87, 0xf9, 0xd7, 0xf3, 0x43, 0x85, 0x0c, 0x7d, 0xc8, 0xdc, 0x24, 0x35, + 0x5e, 0xd3, 0xb1, 0x9e, 0xf2, 0xa9, 0xb8, 0x4d, 0x7b, 0x1d, 0x5f, 0xd1, 0x37, 0x63, 0xa8, 0x83, + 0xe9, 0xf4, 0x75, 0x7a, 0x2a, 0x0e, 0x86, 0x82, 0x63, 0x5c, 0x85, 0x5e, 0x13, 0xc8, 0xf0, 0x20, + 0xf4, 0xcf, 0x72, 0x50, 0xd0, 0x82, 0x15, 0x75, 0x00, 0x81, 0x40, 0x5c, 0xfc, 0xd6, 0x89, 0x87, + 0xdd, 0xba, 0xd5, 0xeb, 0x74, 0xf1, 0x09, 0x2b, 0x51, 0x83, 0x43, 0xc3, 0x4f, 0x14, 0x27, 0xd8, + 0x48, 0xe8, 0xc9, 0xf8, 0x9f, 0x5a, 0xfa, 0xbf, 0xd7, 0x20, 0x1f, 0x27, 0xdb, 0xdb, 0x5d, 0xfd, + 0x6d, 0x5e, 0x86, 0x33, 0x69, 0x73, 0x9c, 0x3c, 0x98, 0x53, 0x28, 0x4b, 0x94, 0x24, 0xff, 0x2b, + 0xcb, 0xfe, 0x5b, 0x0d, 0x56, 0x6b, 0x1e, 0x15, 0xff, 0xeb, 0xbe, 0xe5, 0xe2, 0xb6, 0x30, 0xdc, + 0x76, 0x5c, 0xe7, 0x0e, 0x5d, 0xf8, 0xea, 0x44, 0x5c, 0x67, 0xce, 0x07, 0x52, 0xc3, 0x45, 0x6a, + 0x5c, 0x4b, 0x4e, 0x75, 0x22, 0x6c, 0xb9, 0x40, 0xef, 0xc0, 0xd4, 0x13, 0x92, 0xbf, 0x26, 0xf9, + 0x36, 0x67, 0x14, 0x04, 0x74, 0x64, 0x77, 0xac, 0x1e, 0x11, 0x99, 0x7c, 0x94, 0xd3, 0x30, 0xd3, + 0x30, 0xdd, 0x0e, 0xf6, 0xf5, 0x0f, 0x21, 0x13, 0x0c, 0xd3, 0x32, 0x5e, 0x4a, 0x7f, 0xe4, 0x67, + 0x52, 0x7d, 0xd0, 0x41, 0x51, 0x7d, 0xd0, 0x0f, 0x7d, 0x17, 0xd6, 0x22, 0x6a, 0xf2, 0x35, 0xc8, + 0x13, 0x63, 0x30, 0x18, 0x7b, 0xc8, 0x33, 0x82, 0x6f, 0xbd, 0x02, 0xcb, 0x23, 0x56, 0x44, 0x48, + 0xea, 0x4d, 0x23, 0x1b, 0xa9, 0x5e, 0xaf, 0x12, 0x58, 0xd0, 0x8a, 0x46, 0x60, 0x8d, 0xa3, 0x7a, + 0x79, 0x86, 0xad, 0x0a, 0xc9, 0x45, 0xe4, 0xe4, 0x42, 0x1f, 0x46, 0xbc, 0xf0, 0xfa, 0x23, 0x7a, + 0x96, 0x95, 0xcf, 0xd3, 0x65, 0xc8, 0xd4, 0x7d, 0x93, 0x9e, 0xfb, 0x85, 0x31, 0xc7, 0x5d, 0x07, + 0xa7, 0xbf, 0x1d, 0x14, 0x26, 0xe8, 0x1d, 0x70, 0x48, 0x86, 0x3e, 0x87, 0xd9, 0xfd, 0x5e, 0x9b, + 0x72, 0x98, 0x7c, 0x05, 0x0e, 0x82, 0x88, 0xec, 0x78, 0x2a, 0x32, 0x89, 0xde, 0x1e, 0x6d, 0x24, + 0xcc, 0x18, 0x12, 0x84, 0x9a, 0xd9, 0xba, 0xb4, 0xd8, 0x4b, 0xd0, 0xb4, 0xc1, 0x3e, 0x88, 0x35, + 0xa9, 0x08, 0x8f, 0xf0, 0x4b, 0x5e, 0xc2, 0x04, 0xdf, 0xfa, 0x1f, 0xe8, 0x25, 0xbb, 0xcf, 0xaf, + 0xdd, 0x54, 0x7b, 0x28, 0x1a, 0x6b, 0x6f, 0xac, 0x71, 0xea, 0x75, 0x34, 0x0e, 0x34, 0x9a, 0x4c, + 0xd2, 0x68, 0x2a, 0xa2, 0xd1, 0xc7, 0x30, 0xc3, 0xd4, 0x20, 0xb4, 0x35, 0x1f, 0x5f, 0xb2, 0xb7, + 0xe0, 0x79, 0x83, 0x7d, 0x90, 0x7a, 0xee, 0xc8, 0xf4, 0x28, 0x29, 0x73, 0x46, 0xf1, 0xf9, 0xee, + 0xbb, 0x90, 0x09, 0x7e, 0x3d, 0x82, 0xe4, 0xc6, 0xda, 0x93, 0x5a, 0x83, 0xe5, 0xc6, 0xe3, 0x93, + 0x46, 0x56, 0x43, 0x00, 0x33, 0x7b, 0xfb, 0x47, 0xfb, 0x8d, 0xfd, 0x6c, 0xaa, 0xf8, 0xfb, 0x0f, + 0x61, 0x8e, 0x04, 0x02, 0x7e, 0x09, 0x86, 0x3e, 0x85, 0xc5, 0x3a, 0xee, 0xb5, 0x1f, 0x61, 0xec, + 0x94, 0xba, 0xd6, 0x15, 0xf6, 0x90, 0x88, 0x8e, 0x01, 0x28, 0xbf, 0x3e, 0xa2, 0xfa, 0xfe, 0xa5, + 0xe3, 0xbf, 0xbc, 0xaf, 0xa1, 0x1f, 0xc3, 0x1c, 0xed, 0x03, 0xe5, 0x82, 0xcf, 0xcb, 0xbd, 0xa1, + 0x79, 0xf1, 0x45, 0x07, 0xdf, 0xd7, 0xd0, 0x67, 0x30, 0x7b, 0x88, 0x7d, 0xba, 0xad, 0xbe, 0x17, + 0xf9, 0x8d, 0x8c, 0x5a, 0x2f, 0xf0, 0x57, 0xbe, 0x88, 0xf9, 0xe8, 0xa5, 0x20, 0xaa, 0x40, 0x9a, + 0x93, 0x7b, 0x48, 0x8f, 0xd0, 0x7b, 0x31, 0x0c, 0x56, 0x22, 0x0c, 0x8e, 0x2c, 0xcf, 0x47, 0x3b, + 0x00, 0xec, 0x16, 0x8c, 0x8a, 0x11, 0x9d, 0x23, 0x3f, 0xa2, 0x3b, 0x3a, 0x24, 0xb9, 0x8a, 0xe4, + 0xa2, 0x9b, 0xca, 0x9d, 0x60, 0x2c, 0x74, 0x04, 0x8b, 0xc1, 0x9d, 0xd4, 0xcd, 0x95, 0x48, 0xe2, + 0xf6, 0x09, 0x2c, 0x8b, 0x67, 0x99, 0x20, 0xfd, 0xa0, 0xa4, 0x84, 0x14, 0xac, 0x04, 0x43, 0xc3, + 0x90, 0x97, 0x69, 0xd5, 0x74, 0x82, 0xee, 0x4a, 0x4c, 0x62, 0xb3, 0x60, 0xfe, 0x7b, 0x63, 0x30, + 0x58, 0x1c, 0xbc, 0xaf, 0xbd, 0xaf, 0xa1, 0x2f, 0x60, 0x41, 0x09, 0x92, 0x48, 0x94, 0xad, 0x71, + 0x19, 0x22, 0xbf, 0x19, 0x3f, 0xc8, 0xe3, 0xea, 0x01, 0x51, 0xd7, 0x8f, 0xb4, 0x76, 0xe5, 0xe3, + 0x5a, 0xb8, 0x58, 0xb7, 0x70, 0x5e, 0xbc, 0x69, 0x47, 0x48, 0xf6, 0x61, 0x85, 0xbf, 0x75, 0x28, + 0xdd, 0xfe, 0x09, 0xcd, 0x60, 0x89, 0xd6, 0x7f, 0x08, 0x2b, 0x7c, 0x2d, 0x15, 0x36, 0xd9, 0xe0, + 0x15, 0x9e, 0xf7, 0x0d, 0x25, 0x32, 0xf8, 0x02, 0xd6, 0xea, 0x11, 0x7d, 0x58, 0x77, 0xd6, 0x2d, + 0x95, 0x85, 0xd4, 0x06, 0x96, 0xc8, 0xeb, 0x11, 0xa0, 0x7a, 0xff, 0xec, 0xd2, 0x0a, 0xd8, 0x5d, + 0x59, 0xf8, 0x05, 0xba, 0x13, 0x51, 0x89, 0x00, 0x29, 0x1a, 0x8d, 0x97, 0xf9, 0x04, 0x8d, 0x51, + 0x83, 0xbd, 0xb5, 0xb1, 0x26, 0x0e, 0xd3, 0x31, 0xcf, 0xac, 0xae, 0xe5, 0x5b, 0x98, 0xb8, 0x85, + 0x4c, 0x20, 0x0f, 0x89, 0x15, 0xbc, 0x95, 0x88, 0x81, 0x3e, 0x87, 0x85, 0x43, 0xec, 0x87, 0x9d, + 0x6e, 0x68, 0x63, 0xa4, 0x37, 0x8e, 0xaf, 0x9b, 0xb8, 0xe5, 0x52, 0xdb, 0xeb, 0x6a, 0x90, 0x3d, + 0x71, 0xda, 0xa6, 0x8f, 0x25, 0x16, 0x77, 0x46, 0x58, 0x70, 0x14, 0xd3, 0x35, 0x2f, 0xbd, 0x44, + 0x6b, 0xed, 0xc0, 0xd4, 0xb1, 0xd5, 0xeb, 0x20, 0x71, 0x15, 0x26, 0xf5, 0x28, 0xe5, 0x57, 0x14, + 0x18, 0x77, 0x3d, 0x1f, 0x0a, 0xd7, 0xb4, 0x78, 0xa1, 0x9f, 0x04, 0xb5, 0xc9, 0x4d, 0x5a, 0xc1, + 0xf2, 0xd2, 0xbe, 0x8f, 0x47, 0x6c, 0xee, 0xa2, 0x9f, 0xd3, 0x75, 0x18, 0xc5, 0x40, 0xf7, 0xf8, + 0x5c, 0xe3, 0xfa, 0xc4, 0xf2, 0xb7, 0x13, 0x67, 0x68, 0xee, 0xa2, 0x53, 0xf1, 0xde, 0x17, 0xc3, + 0xfd, 0x1d, 0xa5, 0x05, 0xe5, 0x35, 0x27, 0xd8, 0xa1, 0x71, 0x9e, 0xfe, 0xc6, 0xd4, 0x5a, 0x28, + 0xad, 0xd4, 0x86, 0x93, 0x57, 0x7f, 0xe7, 0x0a, 0xed, 0xd2, 0xc8, 0x4e, 0x9b, 0x3f, 0xd1, 0xba, + 0x4a, 0xe1, 0xc5, 0x93, 0xbc, 0xaf, 0xa1, 0x5d, 0x00, 0x26, 0x25, 0x9d, 0x48, 0x1d, 0x4e, 0x5c, + 0xfd, 0x5d, 0x12, 0xfe, 0xdb, 0xaf, 0x48, 0xf4, 0xb9, 0x48, 0x01, 0x94, 0x28, 0xa7, 0x1c, 0x88, + 0x64, 0xad, 0x92, 0xe8, 0x6b, 0x90, 0x2d, 0xb5, 0x68, 0x40, 0x0b, 0x3a, 0x83, 0xd0, 0x56, 0xb0, + 0x59, 0xd4, 0x01, 0xc1, 0x6b, 0x2d, 0xda, 0x68, 0x74, 0x84, 0x49, 0x85, 0x5b, 0x85, 0x8d, 0x20, + 0x35, 0x45, 0x86, 0xe2, 0x29, 0x12, 0x85, 0xda, 0x87, 0xd5, 0x8a, 0xd9, 0x6b, 0xe1, 0xee, 0x9b, + 0xb1, 0xf9, 0x84, 0xee, 0x6c, 0xa9, 0x6b, 0x6a, 0x3d, 0x4a, 0xcf, 0x37, 0xb6, 0xe8, 0x36, 0x97, + 0x50, 0x4b, 0xb0, 0xc4, 0x8c, 0x18, 0x9a, 0x25, 0x89, 0x3a, 0x69, 0xfa, 0x8f, 0x60, 0x71, 0x9f, + 0x44, 0xbe, 0x7e, 0xdb, 0x62, 0x35, 0x31, 0x52, 0x9b, 0x88, 0x12, 0x09, 0xab, 0xb0, 0xcc, 0x13, + 0x41, 0xd8, 0x4e, 0x14, 0x04, 0xdf, 0xd1, 0x8e, 0xad, 0xfc, 0xaa, 0x60, 0x2b, 0x77, 0x1e, 0x89, + 0x34, 0xa7, 0x74, 0xcf, 0x04, 0x69, 0x2e, 0xae, 0xab, 0x27, 0x48, 0x73, 0xf1, 0x0d, 0x37, 0x65, + 0x58, 0x8a, 0x34, 0xce, 0xa0, 0x3b, 0x22, 0xd9, 0xc6, 0x36, 0xd4, 0xc4, 0x14, 0x2c, 0x55, 0x61, + 0xd5, 0x51, 0x1e, 0xf1, 0x5d, 0x34, 0x89, 0x36, 0x3a, 0x0e, 0xb2, 0x9c, 0xdc, 0x12, 0x83, 0xd4, + 0x1b, 0x81, 0xb8, 0x76, 0x99, 0x44, 0x8e, 0x75, 0x72, 0x78, 0x51, 0x9b, 0x49, 0xd0, 0x56, 0x60, + 0x91, 0xd8, 0x8e, 0x99, 0x7c, 0x21, 0x71, 0x9c, 0x1b, 0x4d, 0x5a, 0x00, 0xf6, 0x7b, 0x65, 0xd1, + 0x05, 0x90, 0x5b, 0x0f, 0x46, 0x16, 0x40, 0xed, 0x28, 0x38, 0xa4, 0xaf, 0x47, 0x52, 0x57, 0x08, + 0x4a, 0x50, 0x25, 0x7f, 0x27, 0x8e, 0x4f, 0xb8, 0x92, 0x75, 0xc8, 0x46, 0x9b, 0x2a, 0x02, 0x4d, + 0x13, 0xba, 0x43, 0x02, 0x4d, 0x13, 0xbb, 0x31, 0xbe, 0x80, 0x6c, 0xb4, 0xa3, 0x22, 0x60, 0x9a, + 0xd0, 0x6a, 0x91, 0xb8, 0x14, 0x07, 0xb0, 0xaa, 0x2e, 0xe0, 0x35, 0xfa, 0x26, 0x57, 0x32, 0x0b, + 0x4a, 0x1f, 0x05, 0x12, 0xa9, 0x21, 0xd2, 0xb2, 0x31, 0x62, 0xfd, 0x98, 0x7e, 0x0e, 0x66, 0x7d, + 0xa9, 0x27, 0xe3, 0x26, 0xd6, 0x8f, 0x6b, 0xe1, 0x08, 0x0c, 0x25, 0xc9, 0x25, 0x22, 0x6e, 0x74, + 0xe0, 0x55, 0x0c, 0x75, 0x13, 0xd1, 0x92, 0xf8, 0xec, 0xc1, 0x9c, 0xd4, 0xcc, 0x81, 0x6e, 0x29, + 0x66, 0x52, 0x3c, 0x3e, 0xaf, 0x28, 0xa7, 0x3a, 0x7b, 0x05, 0xe6, 0xe5, 0x96, 0x90, 0x44, 0x29, + 0x6e, 0x8f, 0xf2, 0xf0, 0xa4, 0x6a, 0x7a, 0x31, 0xb0, 0x02, 0x93, 0x66, 0x33, 0x6a, 0x1c, 0x45, + 0xa0, 0x64, 0x95, 0x90, 0x6c, 0x9a, 0x6b, 0x44, 0x4a, 0xce, 0x44, 0x2b, 0x2c, 0x27, 0xab, 0xbf, + 0xd1, 0x9a, 0xf0, 0x8b, 0xb1, 0x63, 0xa2, 0xd5, 0x52, 0xa4, 0x87, 0x05, 0x49, 0x5e, 0x12, 0xd3, + 0xf7, 0x91, 0xdf, 0x4a, 0x1a, 0xe6, 0x66, 0x3a, 0x82, 0xe5, 0x91, 0x16, 0x16, 0x54, 0x50, 0xe2, + 0xf1, 0x68, 0x2f, 0xca, 0x98, 0xf3, 0xdf, 0xf2, 0x48, 0xff, 0x4a, 0xc0, 0x2d, 0xa9, 0xb3, 0x25, + 0x91, 0x5b, 0x03, 0xd6, 0x62, 0x7b, 0x5a, 0x82, 0xfa, 0x70, 0x5c, 0xc7, 0x4b, 0x22, 0xd7, 0x9f, + 0x03, 0x1a, 0xed, 0x40, 0x09, 0x4e, 0x84, 0x89, 0x3d, 0x32, 0xc1, 0x89, 0x70, 0x4c, 0xfb, 0xca, + 0x11, 0xac, 0xc6, 0xb5, 0x9f, 0x20, 0x5d, 0xb1, 0x67, 0x6c, 0xfb, 0x48, 0x4c, 0x92, 0x33, 0xc4, + 0xa6, 0x4c, 0xe0, 0x36, 0xa6, 0x19, 0x25, 0x51, 0xf9, 0x5f, 0x88, 0x06, 0xa3, 0xd1, 0xa6, 0x91, + 0xa0, 0x2e, 0xbe, 0xa6, 0xab, 0x64, 0x4c, 0xb9, 0xb1, 0x54, 0xb7, 0x3a, 0x3d, 0xa9, 0xbf, 0x23, + 0x28, 0x36, 0x46, 0x1b, 0x4b, 0x82, 0x00, 0x10, 0xd7, 0x0e, 0xf2, 0x14, 0x56, 0x45, 0x26, 0x94, + 0xbb, 0x28, 0xd0, 0x08, 0x4d, 0xd8, 0xe4, 0x11, 0x04, 0x83, 0xd8, 0xb6, 0x0b, 0x56, 0xad, 0xd3, + 0x3f, 0x53, 0x20, 0x55, 0xeb, 0x52, 0x7b, 0x43, 0x5e, 0xed, 0x84, 0x40, 0x0f, 0x68, 0xb5, 0xce, + 0x7a, 0x50, 0x93, 0xf6, 0xfa, 0x86, 0xca, 0x29, 0x74, 0x83, 0x5d, 0x71, 0xff, 0x42, 0x27, 0x54, + 0x39, 0x5f, 0x5f, 0x80, 0x53, 0x22, 0xb5, 0x00, 0x97, 0x05, 0x4d, 0x3e, 0x21, 0xcf, 0xcb, 0x6f, + 0x43, 0x81, 0xad, 0x62, 0x5e, 0x9f, 0x02, 0x5b, 0xc5, 0x3d, 0x26, 0xd1, 0x7a, 0xaf, 0x21, 0xea, + 0xab, 0x90, 0xdf, 0x9d, 0xb1, 0xaf, 0x41, 0xf9, 0xad, 0xf1, 0x4f, 0x28, 0x52, 0x15, 0x19, 0x3e, + 0x5e, 0xc8, 0x45, 0xcc, 0xc8, 0x53, 0x87, 0x9c, 0x46, 0x63, 0xde, 0x3b, 0x1e, 0xd3, 0x2b, 0xe2, + 0xa7, 0xb5, 0xbd, 0x0a, 0xff, 0x73, 0x09, 0xb6, 0x3b, 0x72, 0x71, 0x25, 0xfd, 0xd2, 0x5c, 0x68, + 0x3d, 0x86, 0xa2, 0x10, 0x36, 0x8b, 0xa8, 0x4e, 0xef, 0x5e, 0x14, 0x68, 0xcc, 0xdd, 0x55, 0x0c, + 0xc3, 0x7c, 0x3c, 0x43, 0x7a, 0x0f, 0x47, 0x83, 0x3e, 0xf1, 0x03, 0x55, 0xcc, 0x04, 0x19, 0xc6, + 0xe5, 0x0e, 0x66, 0xd5, 0x78, 0x36, 0x42, 0xba, 0xeb, 0x1c, 0x84, 0x59, 0xac, 0x5e, 0x7a, 0x7c, + 0xf4, 0x5a, 0x16, 0x53, 0x08, 0x03, 0x8b, 0x29, 0xd0, 0x57, 0xb3, 0x58, 0x84, 0xa1, 0x6a, 0x31, + 0x55, 0xcc, 0x04, 0x19, 0xae, 0xb7, 0x58, 0x3c, 0x9b, 0x9b, 0x5a, 0xec, 0x4b, 0x9a, 0x29, 0x0e, + 0xe9, 0x2f, 0xec, 0xbd, 0x92, 0xcd, 0x72, 0xa2, 0xee, 0x51, 0x49, 0x9b, 0xbb, 0xe8, 0x19, 0x6d, + 0x9c, 0x8d, 0xc0, 0x6f, 0x66, 0xb7, 0xcd, 0x24, 0xa6, 0xd4, 0x72, 0x35, 0x58, 0x63, 0x96, 0x8b, + 0x8a, 0x9b, 0x28, 0x4b, 0xa2, 0xda, 0x87, 0x22, 0xed, 0x46, 0x59, 0xbd, 0xaa, 0xfd, 0xf6, 0xa8, + 0x8b, 0x34, 0x5c, 0x52, 0xd5, 0xb4, 0x47, 0x4b, 0x1e, 0x95, 0x89, 0xb8, 0x2d, 0x53, 0xd1, 0x9b, + 0x45, 0x54, 0xa3, 0xab, 0xa0, 0x82, 0xc7, 0xd5, 0x84, 0xf1, 0x6c, 0xa8, 0x91, 0xaa, 0x22, 0x3f, + 0x47, 0x64, 0x4a, 0x9a, 0x3b, 0x59, 0xa8, 0xa0, 0x60, 0xbe, 0xa1, 0x76, 0x49, 0x26, 0x62, 0x79, + 0x86, 0xd5, 0xa7, 0xd7, 0x59, 0x26, 0xfa, 0x87, 0x8c, 0xd0, 0xff, 0x87, 0x8c, 0x20, 0xbe, 0xde, + 0x20, 0x51, 0x6a, 0x6a, 0x90, 0xcf, 0x61, 0x8e, 0x1b, 0x84, 0x4a, 0x90, 0x34, 0x53, 0xa2, 0xf8, + 0x9f, 0xc1, 0x1c, 0x37, 0xc3, 0x58, 0x0d, 0x92, 0xc8, 0x9b, 0xb4, 0x19, 0x38, 0xe1, 0x0f, 0x8d, + 0x24, 0x6a, 0x74, 0xed, 0x9f, 0x39, 0x21, 0x7c, 0xeb, 0xc9, 0x7c, 0xaf, 0xa5, 0x1f, 0x73, 0xb3, + 0x96, 0x09, 0xde, 0x25, 0x91, 0x94, 0xfe, 0x95, 0x97, 0xb9, 0xfc, 0x82, 0xfc, 0x24, 0xe4, 0xa1, + 0x12, 0x8b, 0xbb, 0xf2, 0x1b, 0x9e, 0x74, 0x1f, 0x10, 0xfb, 0xb8, 0x17, 0x61, 0x51, 0xce, 0x7e, + 0xfb, 0x9f, 0x5b, 0xda, 0xb7, 0xdf, 0x6d, 0x69, 0xff, 0xfa, 0xdd, 0x96, 0xf6, 0xc7, 0xef, 0xb6, + 0xb4, 0xb3, 0x19, 0x3a, 0xbe, 0xfb, 0xdf, 0x01, 0x00, 0x00, 0xff, 0xff, 0x89, 0x4e, 0xb4, 0x3a, + 0x7f, 0x4d, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -6547,6 +6785,10 @@ type AuthServiceClient interface { GetClusterNetworkingConfig(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*types.ClusterNetworkingConfigV2, error) // SetClusterNetworkingConfig sets cluster networking configuration. SetClusterNetworkingConfig(ctx context.Context, in *types.ClusterNetworkingConfigV2, opts ...grpc.CallOption) (*empty.Empty, error) + // Out-of-session request for audit events. + GetEvents(ctx context.Context, in *GetEventsRequest, opts ...grpc.CallOption) (*Events, error) + // In-session request for audit events. + GetSessionEvents(ctx context.Context, in *GetSessionEventsRequest, opts ...grpc.CallOption) (*Events, error) } type authServiceClient struct { @@ -7544,6 +7786,24 @@ func (c *authServiceClient) SetClusterNetworkingConfig(ctx context.Context, in * return out, nil } +func (c *authServiceClient) GetEvents(ctx context.Context, in *GetEventsRequest, opts ...grpc.CallOption) (*Events, error) { + out := new(Events) + err := c.cc.Invoke(ctx, "/proto.AuthService/GetEvents", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *authServiceClient) GetSessionEvents(ctx context.Context, in *GetSessionEventsRequest, opts ...grpc.CallOption) (*Events, error) { + out := new(Events) + err := c.cc.Invoke(ctx, "/proto.AuthService/GetSessionEvents", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // AuthServiceServer is the server API for AuthService service. type AuthServiceServer interface { // SendKeepAlives allows node to send a stream of keep alive requests @@ -7754,6 +8014,10 @@ type AuthServiceServer interface { GetClusterNetworkingConfig(context.Context, *empty.Empty) (*types.ClusterNetworkingConfigV2, error) // SetClusterNetworkingConfig sets cluster networking configuration. SetClusterNetworkingConfig(context.Context, *types.ClusterNetworkingConfigV2) (*empty.Empty, error) + // Out-of-session request for audit events. + GetEvents(context.Context, *GetEventsRequest) (*Events, error) + // In-session request for audit events. + GetSessionEvents(context.Context, *GetSessionEventsRequest) (*Events, error) } // UnimplementedAuthServiceServer can be embedded to have forward compatible implementations. @@ -8036,6 +8300,12 @@ func (*UnimplementedAuthServiceServer) GetClusterNetworkingConfig(ctx context.Co func (*UnimplementedAuthServiceServer) SetClusterNetworkingConfig(ctx context.Context, req *types.ClusterNetworkingConfigV2) (*empty.Empty, error) { return nil, status.Errorf(codes.Unimplemented, "method SetClusterNetworkingConfig not implemented") } +func (*UnimplementedAuthServiceServer) GetEvents(ctx context.Context, req *GetEventsRequest) (*Events, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetEvents not implemented") +} +func (*UnimplementedAuthServiceServer) GetSessionEvents(ctx context.Context, req *GetSessionEventsRequest) (*Events, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetSessionEvents not implemented") +} func RegisterAuthServiceServer(s *grpc.Server, srv AuthServiceServer) { s.RegisterService(&_AuthService_serviceDesc, srv) @@ -9743,6 +10013,42 @@ func _AuthService_SetClusterNetworkingConfig_Handler(srv interface{}, ctx contex return interceptor(ctx, in, info, handler) } +func _AuthService_GetEvents_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetEventsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AuthServiceServer).GetEvents(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/proto.AuthService/GetEvents", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AuthServiceServer).GetEvents(ctx, req.(*GetEventsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AuthService_GetSessionEvents_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetSessionEventsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AuthServiceServer).GetSessionEvents(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/proto.AuthService/GetSessionEvents", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AuthServiceServer).GetSessionEvents(ctx, req.(*GetSessionEventsRequest)) + } + return interceptor(ctx, in, info, handler) +} + var _AuthService_serviceDesc = grpc.ServiceDesc{ ServiceName: "proto.AuthService", HandlerType: (*AuthServiceServer)(nil), @@ -10087,6 +10393,14 @@ var _AuthService_serviceDesc = grpc.ServiceDesc{ MethodName: "SetClusterNetworkingConfig", Handler: _AuthService_SetClusterNetworkingConfig_Handler, }, + { + MethodName: "GetEvents", + Handler: _AuthService_GetEvents_Handler, + }, + { + MethodName: "GetSessionEvents", + Handler: _AuthService_GetSessionEvents_Handler, + }, }, Streams: []grpc.StreamDesc{ { @@ -14854,63 +15168,230 @@ func (m *SingleUseUserCert_TLS) MarshalToSizedBuffer(dAtA []byte) (int, error) { } return len(dAtA) - i, nil } -func encodeVarintAuthservice(dAtA []byte, offset int, v uint64) int { - offset -= sovAuthservice(v) - base := offset - for v >= 1<<7 { - dAtA[offset] = uint8(v&0x7f | 0x80) - v >>= 7 - offset++ +func (m *GetEventsRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err } - dAtA[offset] = uint8(v) - return base + return dAtA[:n], nil } -func (m *Event) Size() (n int) { - if m == nil { - return 0 - } + +func (m *GetEventsRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *GetEventsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i var l int _ = l - if m.Type != 0 { - n += 1 + sovAuthservice(uint64(m.Type)) + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) } - if m.Resource != nil { - n += m.Resource.Size() + if len(m.StartKey) > 0 { + i -= len(m.StartKey) + copy(dAtA[i:], m.StartKey) + i = encodeVarintAuthservice(dAtA, i, uint64(len(m.StartKey))) + i-- + dAtA[i] = 0x32 } - if m.XXX_unrecognized != nil { - n += len(m.XXX_unrecognized) + if m.Limit != 0 { + i = encodeVarintAuthservice(dAtA, i, uint64(m.Limit)) + i-- + dAtA[i] = 0x28 } - return n -} - -func (m *Event_ResourceHeader) Size() (n int) { - if m == nil { - return 0 + if len(m.EventTypes) > 0 { + for iNdEx := len(m.EventTypes) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.EventTypes[iNdEx]) + copy(dAtA[i:], m.EventTypes[iNdEx]) + i = encodeVarintAuthservice(dAtA, i, uint64(len(m.EventTypes[iNdEx]))) + i-- + dAtA[i] = 0x22 + } } - var l int - _ = l - if m.ResourceHeader != nil { - l = m.ResourceHeader.Size() - n += 1 + l + sovAuthservice(uint64(l)) + n62, err62 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.EndDate, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.EndDate):]) + if err62 != nil { + return 0, err62 } - return n -} -func (m *Event_CertAuthority) Size() (n int) { - if m == nil { - return 0 + i -= n62 + i = encodeVarintAuthservice(dAtA, i, uint64(n62)) + i-- + dAtA[i] = 0x1a + n63, err63 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.StartDate, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.StartDate):]) + if err63 != nil { + return 0, err63 } - var l int - _ = l - if m.CertAuthority != nil { - l = m.CertAuthority.Size() - n += 1 + l + sovAuthservice(uint64(l)) + i -= n63 + i = encodeVarintAuthservice(dAtA, i, uint64(n63)) + i-- + dAtA[i] = 0x12 + if len(m.Namespace) > 0 { + i -= len(m.Namespace) + copy(dAtA[i:], m.Namespace) + i = encodeVarintAuthservice(dAtA, i, uint64(len(m.Namespace))) + i-- + dAtA[i] = 0xa } - return n + return len(dAtA) - i, nil } -func (m *Event_StaticTokens) Size() (n int) { - if m == nil { - return 0 - } + +func (m *GetSessionEventsRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *GetSessionEventsRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *GetSessionEventsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } + if len(m.StartKey) > 0 { + i -= len(m.StartKey) + copy(dAtA[i:], m.StartKey) + i = encodeVarintAuthservice(dAtA, i, uint64(len(m.StartKey))) + i-- + dAtA[i] = 0x22 + } + if m.Limit != 0 { + i = encodeVarintAuthservice(dAtA, i, uint64(m.Limit)) + i-- + dAtA[i] = 0x18 + } + n64, err64 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.EndDate, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.EndDate):]) + if err64 != nil { + return 0, err64 + } + i -= n64 + i = encodeVarintAuthservice(dAtA, i, uint64(n64)) + i-- + dAtA[i] = 0x12 + n65, err65 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.StartDate, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.StartDate):]) + if err65 != nil { + return 0, err65 + } + i -= n65 + i = encodeVarintAuthservice(dAtA, i, uint64(n65)) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *Events) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Events) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Events) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } + if len(m.LastKey) > 0 { + i -= len(m.LastKey) + copy(dAtA[i:], m.LastKey) + i = encodeVarintAuthservice(dAtA, i, uint64(len(m.LastKey))) + i-- + dAtA[i] = 0x12 + } + if len(m.Items) > 0 { + i -= len(m.Items) + copy(dAtA[i:], m.Items) + i = encodeVarintAuthservice(dAtA, i, uint64(len(m.Items))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func encodeVarintAuthservice(dAtA []byte, offset int, v uint64) int { + offset -= sovAuthservice(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *Event) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Type != 0 { + n += 1 + sovAuthservice(uint64(m.Type)) + } + if m.Resource != nil { + n += m.Resource.Size() + } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + +func (m *Event_ResourceHeader) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.ResourceHeader != nil { + l = m.ResourceHeader.Size() + n += 1 + l + sovAuthservice(uint64(l)) + } + return n +} +func (m *Event_CertAuthority) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.CertAuthority != nil { + l = m.CertAuthority.Size() + n += 1 + l + sovAuthservice(uint64(l)) + } + return n +} +func (m *Event_StaticTokens) Size() (n int) { + if m == nil { + return 0 + } var l int _ = l if m.StaticTokens != nil { @@ -16680,7 +17161,7 @@ func (m *AddMFADeviceRequest_Init) Size() (n int) { return n } func (m *AddMFADeviceRequest_ExistingMFAResponse) Size() (n int) { - if m == nil { + if m == nil { return 0 } var l int @@ -17126,6 +17607,81 @@ func (m *SingleUseUserCert_TLS) Size() (n int) { } return n } +func (m *GetEventsRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Namespace) + if l > 0 { + n += 1 + l + sovAuthservice(uint64(l)) + } + l = github_com_gogo_protobuf_types.SizeOfStdTime(m.StartDate) + n += 1 + l + sovAuthservice(uint64(l)) + l = github_com_gogo_protobuf_types.SizeOfStdTime(m.EndDate) + n += 1 + l + sovAuthservice(uint64(l)) + if len(m.EventTypes) > 0 { + for _, s := range m.EventTypes { + l = len(s) + n += 1 + l + sovAuthservice(uint64(l)) + } + } + if m.Limit != 0 { + n += 1 + sovAuthservice(uint64(m.Limit)) + } + l = len(m.StartKey) + if l > 0 { + n += 1 + l + sovAuthservice(uint64(l)) + } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + +func (m *GetSessionEventsRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = github_com_gogo_protobuf_types.SizeOfStdTime(m.StartDate) + n += 1 + l + sovAuthservice(uint64(l)) + l = github_com_gogo_protobuf_types.SizeOfStdTime(m.EndDate) + n += 1 + l + sovAuthservice(uint64(l)) + if m.Limit != 0 { + n += 1 + sovAuthservice(uint64(m.Limit)) + } + l = len(m.StartKey) + if l > 0 { + n += 1 + l + sovAuthservice(uint64(l)) + } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + +func (m *Events) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Items) + if l > 0 { + n += 1 + l + sovAuthservice(uint64(l)) + } + l = len(m.LastKey) + if l > 0 { + n += 1 + l + sovAuthservice(uint64(l)) + } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} func sovAuthservice(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 @@ -19672,7 +20228,7 @@ func (m *RequestStateSetter) Unmarshal(dAtA []byte) error { } m.Roles = append(m.Roles, string(dAtA[iNdEx:postIndex])) iNdEx = postIndex - default: + default: iNdEx = preIndex skippy, err := skipAuthservice(dAtA[iNdEx:]) if err != nil { @@ -27768,6 +28324,523 @@ func (m *SingleUseUserCert) Unmarshal(dAtA []byte) error { } return nil } +func (m *GetEventsRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthservice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: GetEventsRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GetEventsRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Namespace", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthservice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthAuthservice + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthAuthservice + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Namespace = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field StartDate", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthservice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthAuthservice + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthAuthservice + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := github_com_gogo_protobuf_types.StdTimeUnmarshal(&m.StartDate, dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field EndDate", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthservice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthAuthservice + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthAuthservice + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := github_com_gogo_protobuf_types.StdTimeUnmarshal(&m.EndDate, dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field EventTypes", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthservice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthAuthservice + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthAuthservice + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.EventTypes = append(m.EventTypes, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Limit", wireType) + } + m.Limit = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthservice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Limit |= int32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field StartKey", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthservice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthAuthservice + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthAuthservice + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.StartKey = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipAuthservice(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthAuthservice + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *GetSessionEventsRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthservice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: GetSessionEventsRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GetSessionEventsRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field StartDate", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthservice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthAuthservice + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthAuthservice + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := github_com_gogo_protobuf_types.StdTimeUnmarshal(&m.StartDate, dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field EndDate", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthservice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthAuthservice + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthAuthservice + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := github_com_gogo_protobuf_types.StdTimeUnmarshal(&m.EndDate, dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Limit", wireType) + } + m.Limit = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthservice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Limit |= int32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field StartKey", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthservice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthAuthservice + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthAuthservice + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.StartKey = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipAuthservice(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthAuthservice + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Events) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthservice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Events: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Events: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Items", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthservice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthAuthservice + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthAuthservice + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Items = append(m.Items[:0], dAtA[iNdEx:postIndex]...) + if m.Items == nil { + m.Items = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field LastKey", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthservice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthAuthservice + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthAuthservice + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.LastKey = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipAuthservice(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthAuthservice + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipAuthservice(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/api/client/proto/authservice.proto b/api/client/proto/authservice.proto index 0b45ab8af77e1..0cf9ee7e111d3 100644 --- a/api/client/proto/authservice.proto +++ b/api/client/proto/authservice.proto @@ -804,6 +804,42 @@ message SingleUseUserCert { } } +message GetEventsRequest { + // Namespace, if not set, defaults to 'default' + string Namespace = 1; + // Oldest date of returned events + google.protobuf.Timestamp StartDate = 2 + [ (gogoproto.stdtime) = true, (gogoproto.nullable) = false ]; + // Newest date of returned events + google.protobuf.Timestamp EndDate = 3 + [ (gogoproto.stdtime) = true, (gogoproto.nullable) = false ]; + // EventTypes is optional, if not set, returns all events + repeated string EventTypes = 4; + // Maximum amount of events returned + int32 Limit = 5; + // When supplied the search will resume from the last key + string StartKey = 6; +} + +message GetSessionEventsRequest { + // Oldest date of returned events + google.protobuf.Timestamp StartDate = 1 + [ (gogoproto.stdtime) = true, (gogoproto.nullable) = false ]; + // Newest date of returned events + google.protobuf.Timestamp EndDate = 2 + [ (gogoproto.stdtime) = true, (gogoproto.nullable) = false ]; + int32 Limit = 3; + string StartKey = 4; +} + +message Events { + bytes Items = 1; + // the key of the last event if the returned set did not contain all events found i.e limit < + // actual amount. this is the key clients can supply in another API request to continue fetching + // events from the previous last position + string LastKey = 2; +} + // AuthService is authentication/authorization service implementation service AuthService { // SendKeepAlives allows node to send a stream of keep alive requests @@ -1043,4 +1079,9 @@ service AuthService { rpc GetClusterNetworkingConfig(google.protobuf.Empty) returns (types.ClusterNetworkingConfigV2); // SetClusterNetworkingConfig sets cluster networking configuration. rpc SetClusterNetworkingConfig(types.ClusterNetworkingConfigV2) returns (google.protobuf.Empty); + + // Out-of-session request for audit events. + rpc GetEvents(GetEventsRequest) returns (Events); + // In-session request for audit events. + rpc GetSessionEvents(GetSessionEventsRequest) returns (Events); } diff --git a/e b/e index a7e421cbb066e..7d5351ff4f954 160000 --- a/e +++ b/e @@ -1 +1 @@ -Subproject commit a7e421cbb066ea2b6a4d7ba270d0bbb4b59e8ae2 +Subproject commit 7d5351ff4f954fe8180141e8e3e9b3db9b2c5b5b diff --git a/integration/integration_test.go b/integration/integration_test.go index 6527a434237eb..8ee77a9df18cd 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -1509,12 +1509,12 @@ func (s *IntSuite) twoClustersTunnel(c *check.C, now time.Time, proxyRecordMode stopCh := time.After(5 * time.Second) // only look for exec events - execQuery := fmt.Sprintf("%s=%s", events.EventType, events.ExecEvent) + eventTypes := []string{events.ExecEvent} for { select { case <-tickCh: - eventsInSite, err := site.SearchEvents(now, now.Add(1*time.Hour), execQuery, 0) + eventsInSite, _, err := site.SearchEvents(now, now.Add(1*time.Hour), defaults.Namespace, eventTypes, 0, "") if err != nil { return trace.Wrap(err) } diff --git a/lib/auth/apiserver.go b/lib/auth/apiserver.go index 4b73eea8dfe5b..9cf2d79875aa9 100644 --- a/lib/auth/apiserver.go +++ b/lib/auth/apiserver.go @@ -1819,12 +1819,9 @@ func (s *APIServer) searchEvents(auth ClientI, w http.ResponseWriter, r *http.Re return nil, trace.BadParameter("failed to parse limit: %q", limit) } } - // remove 'to', 'from' and 'limit' fields, passing the rest of the query unmodified - // to whatever pluggable search is implemented by the backend - query.Del("to") - query.Del("from") - query.Del("limit") - eventsList, err := auth.SearchEvents(from, to, query.Encode(), limit) + + eventTypes := query[events.EventType] + eventsList, _, err := auth.SearchEvents(from, to, defaults.Namespace, eventTypes, limit, "") if err != nil { return nil, trace.Wrap(err) } @@ -1864,7 +1861,7 @@ func (s *APIServer) searchSessionEvents(auth ClientI, w http.ResponseWriter, r * } } // only pull back start and end events to build list of completed sessions - eventsList, err := auth.SearchSessionEvents(from, to, limit) + eventsList, _, err := auth.SearchSessionEvents(from, to, limit, "") if err != nil { return nil, trace.Wrap(err) } diff --git a/lib/auth/auth_with_roles.go b/lib/auth/auth_with_roles.go index 0cde4c7a2178b..246437a6421a0 100644 --- a/lib/auth/auth_with_roles.go +++ b/lib/auth/auth_with_roles.go @@ -1996,22 +1996,6 @@ func (a *ServerWithRoles) GetSessionEvents(namespace string, sid session.ID, aft return a.alog.GetSessionEvents(namespace, sid, afterN, includePrintEvents) } -func (a *ServerWithRoles) SearchEvents(from, to time.Time, query string, limit int) ([]events.EventFields, error) { - if err := a.action(defaults.Namespace, services.KindEvent, services.VerbList); err != nil { - return nil, trace.Wrap(err) - } - - return a.alog.SearchEvents(from, to, query, limit) -} - -func (a *ServerWithRoles) SearchSessionEvents(from, to time.Time, limit int) ([]events.EventFields, error) { - if err := a.action(defaults.Namespace, services.KindSession, services.VerbList); err != nil { - return nil, trace.Wrap(err) - } - - return a.alog.SearchSessionEvents(from, to, limit) -} - // GetNamespaces returns a list of namespaces func (a *ServerWithRoles) GetNamespaces() ([]services.Namespace, error) { if err := a.action(defaults.Namespace, services.KindNamespace, services.VerbList); err != nil { @@ -2914,6 +2898,34 @@ func (a *ServerWithRoles) IsMFARequired(ctx context.Context, req *proto.IsMFAReq return a.authServer.isMFARequired(ctx, a.context.Checker, req) } +// SearchEvents allows searching audit events with pagination support. +func (a *ServerWithRoles) SearchEvents(fromUTC, toUTC time.Time, namespace string, eventTypes []string, limit int, startKey string) (events []events.EventFields, lastKey string, err error) { + if err := a.action(defaults.Namespace, services.KindEvent, services.VerbList); err != nil { + return nil, "", trace.Wrap(err) + } + + events, lastKey, err = a.alog.SearchEvents(fromUTC, toUTC, namespace, eventTypes, limit, startKey) + if err != nil { + return nil, "", trace.Wrap(err) + } + + return events, lastKey, nil +} + +// SearchSessionEvents allows searching session audit events with pagination support. +func (a *ServerWithRoles) SearchSessionEvents(fromUTC, toUTC time.Time, limit int, startKey string) (events []events.EventFields, lastKey string, err error) { + if err := a.action(defaults.Namespace, services.KindEvent, services.VerbList); err != nil { + return nil, "", trace.Wrap(err) + } + + events, lastKey, err = a.alog.SearchSessionEvents(fromUTC, toUTC, limit, startKey) + if err != nil { + return nil, "", trace.Wrap(err) + } + + return events, lastKey, nil +} + // NewAdminAuthServer returns auth server authorized as admin, // used for auth server cached access func NewAdminAuthServer(authServer *Server, sessions session.Service, alog events.IAuditLog) (ClientI, error) { diff --git a/lib/auth/clt.go b/lib/auth/clt.go index 74206e27ef3c6..23de4f0fc4a90 100644 --- a/lib/auth/clt.go +++ b/lib/auth/clt.go @@ -1451,45 +1451,36 @@ func (c *Client) GetSessionEvents(namespace string, sid session.ID, afterN int, return retval, nil } -// SearchEvents returns events that fit the criteria -func (c *Client) SearchEvents(from, to time.Time, query string, limit int) ([]events.EventFields, error) { - q, err := url.ParseQuery(query) +// SearchEvents allows searching for audit events with pagination support. +func (c *Client) SearchEvents(fromUTC, toUTC time.Time, namespace string, eventTypes []string, limit int, startKey string) ([]events.EventFields, string, error) { + eventsOpaque, lastKey, err := c.APIClient.SearchEvents(context.TODO(), fromUTC, toUTC, namespace, eventTypes, limit, startKey) if err != nil { - return nil, trace.BadParameter("query") + return nil, "", trace.Wrap(err) } - q.Set("from", from.Format(time.RFC3339)) - q.Set("to", to.Format(time.RFC3339)) - q.Set("limit", fmt.Sprintf("%v", limit)) - response, err := c.Get(c.Endpoint("events"), q) - if err != nil { - return nil, trace.Wrap(err) - } - retval := make([]events.EventFields, 0) - if err := json.Unmarshal(response.Bytes(), &retval); err != nil { - return nil, trace.Wrap(err) + + var eventsConcrete []events.EventFields + + for _, event := range eventsOpaque { + eventsConcrete = append(eventsConcrete, events.EventFields(event)) } - return retval, nil + + return eventsConcrete, lastKey, nil } // SearchSessionEvents returns session related events to find completed sessions. -func (c *Client) SearchSessionEvents(from, to time.Time, limit int) ([]events.EventFields, error) { - query := url.Values{ - "to": []string{to.Format(time.RFC3339)}, - "from": []string{from.Format(time.RFC3339)}, - "limit": []string{fmt.Sprintf("%v", limit)}, - } - - response, err := c.Get(c.Endpoint("events", "session"), query) +func (c *Client) SearchSessionEvents(fromUTC time.Time, toUTC time.Time, limit int, startKey string) ([]events.EventFields, string, error) { + eventsOpaque, lastKey, err := c.APIClient.SearchSessionEvents(context.TODO(), fromUTC, toUTC, limit, startKey) if err != nil { - return nil, trace.Wrap(err) + return nil, "", trace.Wrap(err) } - retval := make([]events.EventFields, 0) - if err := json.Unmarshal(response.Bytes(), &retval); err != nil { - return nil, trace.Wrap(err) + var eventsConcrete []events.EventFields + + for _, event := range eventsOpaque { + eventsConcrete = append(eventsConcrete, events.EventFields(event)) } - return retval, nil + return eventsConcrete, lastKey, nil } // GetNamespaces returns a list of namespaces diff --git a/lib/auth/grpcserver.go b/lib/auth/grpcserver.go index 13faa44f8b582..e49a62080c500 100644 --- a/lib/auth/grpcserver.go +++ b/lib/auth/grpcserver.go @@ -19,6 +19,7 @@ package auth import ( "context" "crypto/tls" + "encoding/json" "io" "net" "time" @@ -2512,6 +2513,54 @@ func (g *GRPCServer) authenticate(ctx context.Context) (*grpcContext, error) { }, nil } +// GetEvents searches for events on the backend and sends them back in a response. +func (g *GRPCServer) GetEvents(ctx context.Context, req *proto.GetEventsRequest) (*proto.Events, error) { + auth, err := g.authenticate(ctx) + if err != nil { + return nil, trail.ToGRPC(err) + } + + events, lastkey, err := auth.ServerWithRoles.SearchEvents(req.StartDate, req.EndDate, req.Namespace, req.EventTypes, int(req.Limit), req.StartKey) + if err != nil { + return nil, trail.ToGRPC(err) + } + + var res *proto.Events = &proto.Events{} + + encodedEvents, err := json.Marshal(events) + if err != nil { + return nil, trail.ToGRPC(err) + } + + res.Items = encodedEvents + res.LastKey = lastkey + return res, nil +} + +// GetEvents searches for session events on the backend and sends them back in a response. +func (g *GRPCServer) GetSessionEvents(ctx context.Context, req *proto.GetSessionEventsRequest) (*proto.Events, error) { + auth, err := g.authenticate(ctx) + if err != nil { + return nil, trail.ToGRPC(err) + } + + events, lastkey, err := auth.ServerWithRoles.SearchSessionEvents(req.StartDate, req.EndDate, int(req.Limit), req.StartKey) + if err != nil { + return nil, trail.ToGRPC(err) + } + + var res *proto.Events = &proto.Events{} + + encodedEvents, err := json.Marshal(events) + if err != nil { + return nil, trail.ToGRPC(err) + } + + res.Items = encodedEvents + res.LastKey = lastkey + return res, nil +} + // GRPCServerConfig specifies GRPC server configuration type GRPCServerConfig struct { // APIConfig is GRPC server API configuration diff --git a/lib/auth/tls_test.go b/lib/auth/tls_test.go index 52d49de31b8e4..4303fb1dfd687 100644 --- a/lib/auth/tls_test.go +++ b/lib/auth/tls_test.go @@ -1347,15 +1347,14 @@ func (s *TLSSuite) TestSharedSessions(c *check.C) { // try searching for events with no filter (empty query) - should get all 3 events: to := time.Now().In(time.UTC).Add(time.Hour) from := to.Add(-time.Hour * 2) - history, err := clt.SearchEvents(from, to, "", 0) + history, _, err := clt.SearchEvents(from, to, defaults.Namespace, nil, 0, "") c.Assert(err, check.IsNil) c.Assert(history, check.NotNil) // Extra event is the upload event c.Assert(len(history), check.Equals, 5) // try searching for only "session.end" events (real query) - history, err = clt.SearchEvents(from, to, - fmt.Sprintf("%s=%s", events.EventType, events.SessionEndEvent), 0) + history, _, err = clt.SearchEvents(from, to, defaults.Namespace, []string{events.SessionEndEvent}, 0, "") c.Assert(err, check.IsNil) c.Assert(history, check.NotNil) c.Assert(len(history), check.Equals, 2) diff --git a/lib/events/api.go b/lib/events/api.go index 3848e6b6b803d..d8f970f1403cb 100644 --- a/lib/events/api.go +++ b/lib/events/api.go @@ -582,19 +582,22 @@ type IAuditLog interface { // replay recorded session streams. GetSessionEvents(namespace string, sid session.ID, after int, includePrintEvents bool) ([]EventFields, error) - // SearchEvents is a flexible way to find events. The format of a query string - // depends on the implementing backend. A recommended format is urlencoded - // (good enough for Lucene/Solr) + // SearchEvents is a flexible way to find events. // - // Pagination is also defined via backend-specific query format. + // Event types to filter can be specified and pagination is handled by an iterator key that allows + // a query to be resumed. // // The only mandatory requirement is a date range (UTC). Results must always // show up sorted by date (newest first) - SearchEvents(fromUTC, toUTC time.Time, query string, limit int) ([]EventFields, error) + SearchEvents(fromUTC, toUTC time.Time, namespace string, eventTypes []string, limit int, startKey string) ([]EventFields, string, error) - // SearchSessionEvents returns session related events only. This is used to - // find completed session. - SearchSessionEvents(fromUTC time.Time, toUTC time.Time, limit int) ([]EventFields, error) + // SearchSessionEvents is a flexible way to find session events. + // Only session events are returned by this function. + // This is used to find completed session. + // + // Event types to filter can be specified and pagination is handled by an iterator key that allows + // a query to be resumed. + SearchSessionEvents(fromUTC time.Time, toUTC time.Time, limit int, startKey string) ([]EventFields, string, error) // WaitForDelivery waits for resources to be released and outstanding requests to // complete after calling Close method diff --git a/lib/events/auditlog.go b/lib/events/auditlog.go index 2c5364ff3f2fb..fe8e9a9ef4a6c 100644 --- a/lib/events/auditlog.go +++ b/lib/events/auditlog.go @@ -1016,30 +1016,27 @@ func (l *AuditLog) auditDirs() ([]string, error) { return out, nil } -// SearchEvents finds events. Results show up sorted by date (newest first), -// limit is used when set to value > 0 -func (l *AuditLog) SearchEvents(fromUTC, toUTC time.Time, query string, limit int) ([]EventFields, error) { - l.log.Debugf("SearchEvents(%v, %v, query=%v, limit=%v)", fromUTC, toUTC, query, limit) +func (l *AuditLog) SearchEvents(fromUTC, toUTC time.Time, namespace string, eventType []string, limit int, startKey string) ([]EventFields, string, error) { + l.log.Debugf("SearchEvents(%v, %v, namespace=%v, eventType=%v, limit=%v)", fromUTC, toUTC, namespace, eventType, limit) if limit <= 0 { limit = defaults.EventsIterationLimit } if limit > defaults.EventsMaxIterationLimit { - return nil, trace.BadParameter("limit %v exceeds max iteration limit %v", limit, defaults.MaxIterationLimit) + return nil, "", trace.BadParameter("limit %v exceeds max iteration limit %v", limit, defaults.MaxIterationLimit) } if l.ExternalLog != nil { - return l.ExternalLog.SearchEvents(fromUTC, toUTC, query, limit) + return l.ExternalLog.SearchEvents(fromUTC, toUTC, namespace, eventType, limit, startKey) } - return l.getLocalLog().SearchEvents(fromUTC, toUTC, query, limit) + return l.localLog.SearchEvents(fromUTC, toUTC, namespace, eventType, limit, startKey) } -// SearchSessionEvents searches for session related events. Used to find completed sessions. -func (l *AuditLog) SearchSessionEvents(fromUTC, toUTC time.Time, limit int) ([]EventFields, error) { +func (l *AuditLog) SearchSessionEvents(fromUTC, toUTC time.Time, limit int, startKey string) ([]EventFields, string, error) { l.log.Debugf("SearchSessionEvents(%v, %v, %v)", fromUTC, toUTC, limit) if l.ExternalLog != nil { - return l.ExternalLog.SearchSessionEvents(fromUTC, toUTC, limit) + return l.ExternalLog.SearchSessionEvents(fromUTC, toUTC, limit, startKey) } - return l.getLocalLog().SearchSessionEvents(fromUTC, toUTC, limit) + return l.localLog.SearchSessionEvents(fromUTC, toUTC, limit, startKey) } // getLocalLog returns the local (file based) audit log. @@ -1193,45 +1190,47 @@ func (l *LegacyHandler) Download(ctx context.Context, sessionID session.ID, writ return l.cfg.Handler.Download(ctx, sessionID, writer) } +const loggerClosedMessage = "the logger has been closed" + type closedLogger struct { } func (a *closedLogger) EmitAuditEventLegacy(e Event, f EventFields) error { - return trace.NotImplemented("the logger has been closed") + return trace.NotImplemented(loggerClosedMessage) } func (a *closedLogger) EmitAuditEvent(ctx context.Context, e AuditEvent) error { - return trace.NotImplemented("the logger has been closed") + return trace.NotImplemented(loggerClosedMessage) } func (a *closedLogger) PostSessionSlice(s SessionSlice) error { - return trace.NotImplemented("the logger has been closed") + return trace.NotImplemented(loggerClosedMessage) } func (a *closedLogger) UploadSessionRecording(r SessionRecording) error { - return trace.NotImplemented("the logger has been closed") + return trace.NotImplemented(loggerClosedMessage) } func (a *closedLogger) GetSessionChunk(namespace string, sid session.ID, offsetBytes int, maxBytes int) ([]byte, error) { - return nil, trace.NotImplemented("the logger has been closed") + return nil, trace.NotImplemented(loggerClosedMessage) } func (a *closedLogger) GetSessionEvents(namespace string, sid session.ID, after int, includePrintEvents bool) ([]EventFields, error) { - return nil, trace.NotImplemented("the logger has been closed") + return nil, trace.NotImplemented(loggerClosedMessage) } -func (a *closedLogger) SearchEvents(fromUTC, toUTC time.Time, query string, limit int) ([]EventFields, error) { - return nil, trace.NotImplemented("the logger has been closed") +func (a *closedLogger) SearchEvents(fromUTC, toUTC time.Time, namespace string, eventType []string, limit int, startKey string) ([]EventFields, string, error) { + return nil, "", trace.NotImplemented(loggerClosedMessage) } -func (a *closedLogger) SearchSessionEvents(fromUTC time.Time, toUTC time.Time, limit int) ([]EventFields, error) { - return nil, trace.NotImplemented("the logger has been closed") +func (a *closedLogger) SearchSessionEvents(fromUTC time.Time, toUTC time.Time, limit int, startKey string) ([]EventFields, string, error) { + return nil, "", trace.NotImplemented(loggerClosedMessage) } func (a *closedLogger) WaitForDelivery(context.Context) error { - return trace.NotImplemented("the logger has been closed") + return trace.NotImplemented(loggerClosedMessage) } func (a *closedLogger) Close() error { - return trace.NotImplemented("the logger has been closed") + return trace.NotImplemented(loggerClosedMessage) } diff --git a/lib/events/auditlog_test.go b/lib/events/auditlog_test.go index 0cc01d290c26d..a92981a4672ce 100644 --- a/lib/events/auditlog_test.go +++ b/lib/events/auditlog_test.go @@ -296,7 +296,7 @@ func (a *AuditTestSuite) TestSessionRecordingOff(c *check.C) { upload(c, uploadDir, fakeClock, alog) // get all events from the audit log, should have two session event and one upload event - found, err := alog.SearchEvents(now.Add(-time.Hour), now.Add(time.Hour), "", 0) + found, _, err := alog.SearchEvents(now.Add(-time.Hour), now.Add(time.Hour), defaults.Namespace, nil, 0, "") c.Assert(err, check.IsNil) c.Assert(found, check.HasLen, 3) c.Assert(found[0].GetString(EventLogin), check.Equals, username) @@ -371,7 +371,7 @@ func (a *AuditTestSuite) TestLogRotation(c *check.C) { c.Assert(err, check.IsNil) c.Assert(string(bytes), check.Equals, contents) - found, err := alog.SearchEvents(now.Add(-time.Hour), now.Add(time.Hour), "", 0) + found, _, err := alog.SearchEvents(now.Add(-time.Hour), now.Add(time.Hour), defaults.Namespace, nil, 0, "") c.Assert(err, check.IsNil) c.Assert(found, check.HasLen, 1) } diff --git a/lib/events/discard.go b/lib/events/discard.go index 98023b2773802..15bd3c3c78fd6 100644 --- a/lib/events/discard.go +++ b/lib/events/discard.go @@ -51,11 +51,11 @@ func (d *DiscardAuditLog) GetSessionChunk(namespace string, sid session.ID, offs func (d *DiscardAuditLog) GetSessionEvents(namespace string, sid session.ID, after int, includePrintEvents bool) ([]EventFields, error) { return make([]EventFields, 0), nil } -func (d *DiscardAuditLog) SearchEvents(fromUTC, toUTC time.Time, query string, limit int) ([]EventFields, error) { - return make([]EventFields, 0), nil +func (d *DiscardAuditLog) SearchEvents(fromUTC, toUTC time.Time, namespace string, eventType []string, limit int, startKey string) ([]EventFields, string, error) { + return make([]EventFields, 0), "", nil } -func (d *DiscardAuditLog) SearchSessionEvents(fromUTC time.Time, toUTC time.Time, limit int) ([]EventFields, error) { - return make([]EventFields, 0), nil +func (d *DiscardAuditLog) SearchSessionEvents(fromUTC time.Time, toUTC time.Time, limit int, startKey string) ([]EventFields, string, error) { + return make([]EventFields, 0), "", nil } func (d *DiscardAuditLog) UploadSessionRecording(SessionRecording) error { diff --git a/lib/events/dynamoevents/dynamoevents.go b/lib/events/dynamoevents/dynamoevents.go index 59ca7b4635f0c..3981ada65b7ed 100644 --- a/lib/events/dynamoevents/dynamoevents.go +++ b/lib/events/dynamoevents/dynamoevents.go @@ -20,6 +20,7 @@ package dynamoevents import ( "context" "encoding/json" + "math" "net/url" "sort" "time" @@ -642,39 +643,60 @@ func daysBetween(start, end time.Time) []string { return days } -// SearchEvents is a flexible way to find The format of a query string -// depends on the implementing backend. A recommended format is urlencoded -// (good enough for Lucene/Solr) +type checkpointKey struct { + // The date that the Dynamo iterator corresponds to. + Date string `json:"date,omitempty"` + + // A DynamoDB query iterator. Allows us to resume a partial query. + Iterator map[string]*dynamodb.AttributeValue `json:"iterator,omitempty"` +} + +// SearchEvents is a flexible way to find events. // -// Pagination is also defined via backend-specific query format. +// Event types to filter can be specified and pagination is handled by an iterator key that allows +// a query to be resumed. // // The only mandatory requirement is a date range (UTC). Results must always // show up sorted by date (newest first) -func (l *Log) SearchEvents(fromUTC, toUTC time.Time, filter string, limit int) ([]events.EventFields, error) { - g := l.WithFields(log.Fields{"From": fromUTC, "To": toUTC, "Filter": filter, "Limit": limit}) - filterVals, err := url.ParseQuery(filter) - if err != nil { - return nil, trace.BadParameter("missing parameter query") - } - eventFilter, ok := filterVals[events.EventType] - if !ok && len(filterVals) > 0 { - return nil, nil +func (l *Log) SearchEvents(fromUTC, toUTC time.Time, namespace string, eventTypes []string, limit int, startKey string) ([]events.EventFields, string, error) { + var checkpoint checkpointKey + + // If a checkpoint key is provided, unmarshal it so we can work with it's parts. + if startKey != "" { + if err := json.Unmarshal([]byte(startKey), &checkpoint); err != nil { + return nil, "", trace.Wrap(err) + } } - doFilter := len(eventFilter) > 0 var values []events.EventFields dates := daysBetween(fromUTC, toUTC) query := "CreatedAtDate = :date AND CreatedAt BETWEEN :start and :end" + g := l.WithFields(log.Fields{"From": fromUTC, "To": toUTC, "Namespace": namespace, "EventTypes": eventTypes, "Limit": limit, "StartKey": startKey}) + var left int64 + if limit != 0 { + left = int64(limit) + } else { + left = math.MaxInt64 + } + doFilter := len(eventTypes) > 0 + + // Resume scanning at the correct date. We need to do this because we send individual queries per date + // and you can't resume a query with the wrong iterator checkpoint. + // + // We need to perform a guard check on the length of `dates` here in case a query is submitted with + // `toUTC` occurring before `fromUTC`. + if checkpoint.Date != "" && len(dates) > 0 { + for dates[0] != checkpoint.Date { + dates = dates[1:] + } + } - var total int - + // This is the main query loop, here we send individual queries for each date and + // we stop if we hit `limit` or process all dates, whichever comes first. dateLoop: for _, date := range dates { - if limit > 0 && total >= limit { - break dateLoop - } + checkpoint.Date = date - var lastEvaluatedKey map[string]*dynamodb.AttributeValue attributes := map[string]interface{}{ ":date": date, ":start": fromUTC.Unix(), @@ -683,80 +705,208 @@ dateLoop: attributeValues, err := dynamodbattribute.MarshalMap(attributes) if err != nil { - return nil, trace.Wrap(err) + return nil, "", trace.Wrap(err) } - // Because the maximum size of the dynamo db response size is 900K according to documentation, - // we arbitrary limit the total size to 100MB to prevent runaway loops. - pageLoop: - for pageCount := 0; pageCount < 100; pageCount++ { + for { input := dynamodb.QueryInput{ KeyConditionExpression: aws.String(query), TableName: aws.String(l.Tablename), ExpressionAttributeValues: attributeValues, IndexName: aws.String(indexTimeSearchV2), - ExclusiveStartKey: lastEvaluatedKey, + ExclusiveStartKey: checkpoint.Iterator, + Limit: aws.Int64(left), } + start := time.Now() out, err := l.svc.Query(&input) if err != nil { - return nil, trace.Wrap(err) + return nil, "", trace.Wrap(err) } g.WithFields(log.Fields{"duration": time.Since(start), "items": len(out.Items)}).Debugf("Query completed.") + checkpoint.Iterator = out.LastEvaluatedKey - itemLoop: for _, item := range out.Items { var e event if err := dynamodbattribute.UnmarshalMap(item, &e); err != nil { - return nil, trace.BadParameter("failed to unmarshal event for %v", err) + return nil, "", trace.WrapWithMessage(err, "failed to unmarshal event") } var fields events.EventFields data := []byte(e.Fields) if err := json.Unmarshal(data, &fields); err != nil { - return nil, trace.BadParameter("failed to unmarshal event %v", err) + return nil, "", trace.BadParameter("failed to unmarshal event %v", err) } - var accepted bool - for i := range eventFilter { - if fields.GetString(events.EventType) == eventFilter[i] { + accepted := false + for i := range eventTypes { + if fields.GetString(events.EventType) == eventTypes[i] { accepted = true - break itemLoop + break } } if accepted || !doFilter { values = append(values, fields) - total++ - if limit > 0 && total >= limit { + left-- + if left == 0 { break dateLoop } } } - // AWS returns a `lastEvaluatedKey` in case the response is truncated, i.e. needs to be fetched with - // multiple requests. According to their documentation, the final response is signaled by not setting - // this value - therefore we use it as our break condition. - lastEvaluatedKey = out.LastEvaluatedKey - if len(lastEvaluatedKey) == 0 { - break pageLoop + if len(checkpoint.Iterator) == 0 { + continue dateLoop } } + } - g.Error("DynamoDB response size exceeded limit.") + // When no events are left we set the checkpoint to null + if len(values) == 0 { + checkpoint = checkpointKey{} } + // Sort the events since Dynamo does not guarantee ordering with the used query. sort.Sort(events.ByTimeAndIndex(values)) - return values, nil + + var lastKey []byte + var err error + + if len(values) > 0 { + lastKey, err = json.Marshal(&checkpoint) + if err != nil { + return nil, "", trace.Wrap(err) + } + } + + return values, string(lastKey), nil +} + +// Used in tests to check full backend data. +func (l *Log) searchEventsRaw(fromUTC, toUTC time.Time, namespace string, eventTypes []string, limit int, startKey string) ([]event, string, error) { + var checkpoint checkpointKey + + // If a checkpoint key is provided, unmarshal it so we can work with it's parts. + if startKey != "" { + if err := json.Unmarshal([]byte(startKey), &checkpoint); err != nil { + return nil, "", trace.Wrap(err) + } + } + + var values []event + dates := daysBetween(fromUTC, toUTC) + query := "CreatedAtDate = :date AND CreatedAt BETWEEN :start and :end" + g := l.WithFields(log.Fields{"From": fromUTC, "To": toUTC, "Namespace": namespace, "EventTypes": eventTypes, "Limit": limit, "StartKey": startKey}) + var left int64 + if limit != 0 { + left = int64(limit) + } else { + left = math.MaxInt64 + } + doFilter := len(eventTypes) > 0 + + // Resume scanning at the correct date. We need to do this because we send individual queries per date + // and you can't resume a query with the wrong iterator checkpoint. + // + // We need to perform a guard check on the length of `dates` here in case a query is submitted with + // `toUTC` occurring before `fromUTC`. + if checkpoint.Date != "" && len(dates) > 0 { + for dates[0] != checkpoint.Date { + dates = dates[1:] + } + } + + // This is the main query loop, here we send individual queries for each date and + // we stop if we hit `limit` or process all dates, whichever comes first. +dateLoop: + for _, date := range dates { + checkpoint.Date = date + + attributes := map[string]interface{}{ + ":date": date, + ":start": fromUTC.Unix(), + ":end": toUTC.Unix(), + } + + attributeValues, err := dynamodbattribute.MarshalMap(attributes) + if err != nil { + return nil, "", trace.Wrap(err) + } + + for { + input := dynamodb.QueryInput{ + KeyConditionExpression: aws.String(query), + TableName: aws.String(l.Tablename), + ExpressionAttributeValues: attributeValues, + IndexName: aws.String(indexTimeSearchV2), + ExclusiveStartKey: checkpoint.Iterator, + Limit: aws.Int64(left), + } + + start := time.Now() + out, err := l.svc.Query(&input) + if err != nil { + return nil, "", trace.Wrap(err) + } + g.WithFields(log.Fields{"duration": time.Since(start), "items": len(out.Items)}).Debugf("Query completed.") + checkpoint.Iterator = out.LastEvaluatedKey + + for _, item := range out.Items { + var e event + if err := dynamodbattribute.UnmarshalMap(item, &e); err != nil { + return nil, "", trace.WrapWithMessage(err, "failed to unmarshal event") + } + var fields events.EventFields + data := []byte(e.Fields) + if err := json.Unmarshal(data, &fields); err != nil { + return nil, "", trace.BadParameter("failed to unmarshal event %v", err) + } + accepted := false + for i := range eventTypes { + if e.EventType == eventTypes[i] { + accepted = true + break + } + } + if accepted || !doFilter { + values = append(values, e) + left-- + if left == 0 { + break dateLoop + } + } + } + + if len(checkpoint.Iterator) == 0 { + continue dateLoop + } + } + } + + // When no events are left we set the checkpoint to null + if len(values) == 0 { + checkpoint = checkpointKey{} + } + + var lastKey []byte + var err error + + if len(values) > 0 { + lastKey, err = json.Marshal(&checkpoint) + if err != nil { + return nil, "", trace.Wrap(err) + } + } + + return values, string(lastKey), nil } // SearchSessionEvents returns session related events only. This is used to // find completed session. -func (l *Log) SearchSessionEvents(fromUTC time.Time, toUTC time.Time, limit int) ([]events.EventFields, error) { +func (l *Log) SearchSessionEvents(fromUTC time.Time, toUTC time.Time, limit int, startKey string) ([]events.EventFields, string, error) { // only search for specific event types - query := url.Values{} - query[events.EventType] = []string{ + query := []string{ events.SessionStartEvent, events.SessionEndEvent, } - return l.SearchEvents(fromUTC, toUTC, query.Encode(), limit) + return l.SearchEvents(fromUTC, toUTC, defaults.Namespace, query, limit, startKey) } // WaitForDelivery waits for resources to be released and outstanding requests to diff --git a/lib/events/dynamoevents/dynamoevents_test.go b/lib/events/dynamoevents/dynamoevents_test.go index fd9ae8266396b..6bd540b082a0f 100644 --- a/lib/events/dynamoevents/dynamoevents_test.go +++ b/lib/events/dynamoevents/dynamoevents_test.go @@ -19,8 +19,10 @@ package dynamoevents import ( "context" + "encoding/json" "fmt" "os" + "sort" "strconv" "testing" "time" @@ -29,6 +31,7 @@ import ( "github.com/aws/aws-sdk-go/service/dynamodb" "github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute" "github.com/gravitational/teleport" + "github.com/gravitational/teleport/lib/defaults" "github.com/gravitational/teleport/lib/events" "github.com/gravitational/teleport/lib/events/test" "github.com/gravitational/teleport/lib/utils" @@ -101,7 +104,7 @@ func (s *DynamoeventsSuite) TestSessionEventsCRUD(c *check.C) { time.Sleep(s.EventsSuite.QueryDelay) - history, err := s.Log.SearchEvents(s.Clock.Now().Add(-1*time.Hour), s.Clock.Now().Add(time.Hour), "", 0) + history, _, err := s.Log.SearchEvents(s.Clock.Now().Add(-1*time.Hour), s.Clock.Now().Add(time.Hour), defaults.Namespace, nil, 0, "") c.Assert(err, check.IsNil) // `check.HasLen` prints the entire array on failure, which pollutes the output @@ -165,19 +168,15 @@ func (s *DynamoeventsSuite) TestEventMigration(c *check.C) { waitStart := time.Now() for time.Since(waitStart) < attemptWaitFor { - events, err := s.log.SearchEvents(start, end, "?event=test.event", 1000) + events, _, err := s.log.searchEventsRaw(start, end, defaults.Namespace, []string{"test.event"}, 1000, "") + sort.Sort(byTimeAndIndexRaw(events)) c.Assert(err, check.IsNil) correct := true for _, event := range events { - timestampUnix := event[keyCreatedAt].(int64) + timestampUnix := event.CreatedAt dateString := time.Unix(timestampUnix, 0).Format(iso8601DateFormat) - eventDateInterface, ok := event[keyDate] - if !ok { - correct = false - } - eventDateString, ok := eventDateInterface.(string) - if !ok || dateString != eventDateString { + if dateString != event.CreatedAtDate { correct = false } } @@ -192,6 +191,57 @@ func (s *DynamoeventsSuite) TestEventMigration(c *check.C) { c.Error("Events failed to migrate within 5 minutes") } +type byTimeAndIndexRaw []event + +func (f byTimeAndIndexRaw) Len() int { + return len(f) +} + +func (f byTimeAndIndexRaw) Less(i, j int) bool { + var fi events.EventFields + data := []byte(f[i].Fields) + if err := json.Unmarshal(data, &fi); err != nil { + panic("failed to unmarshal event") + } + var fj events.EventFields + data = []byte(f[j].Fields) + if err := json.Unmarshal(data, &fj); err != nil { + panic("failed to unmarshal event") + } + + itime := getTime(fi[events.EventTime]) + jtime := getTime(fj[events.EventTime]) + if itime.Equal(jtime) && fi[events.SessionEventID] == fj[events.SessionEventID] { + return getEventIndex(fi[events.EventIndex]) < getEventIndex(fj[events.EventIndex]) + } + return itime.Before(jtime) +} + +func (f byTimeAndIndexRaw) Swap(i, j int) { + f[i], f[j] = f[j], f[i] +} + +// getTime converts json time to string +func getTime(v interface{}) time.Time { + sval, ok := v.(string) + if !ok { + return time.Time{} + } + t, err := time.Parse(time.RFC3339, sval) + if err != nil { + return time.Time{} + } + return t +} + +func getEventIndex(v interface{}) float64 { + switch val := v.(type) { + case float64: + return val + } + return 0 +} + type preRFD24event struct { SessionID string EventIndex int64 diff --git a/lib/events/filelog.go b/lib/events/filelog.go index 5cbea71be0592..5385962573c7f 100644 --- a/lib/events/filelog.go +++ b/lib/events/filelog.go @@ -21,7 +21,6 @@ import ( "context" "encoding/json" "fmt" - "net/url" "os" "path/filepath" "sort" @@ -195,36 +194,33 @@ func (l *FileLog) EmitAuditEventLegacy(event Event, fields EventFields) error { return nil } -// SearchEvents finds events. Results show up sorted by date (newest first), -// limit is used when set to value > 0 -func (l *FileLog) SearchEvents(fromUTC, toUTC time.Time, query string, limit int) ([]EventFields, error) { - l.Debugf("SearchEvents(%v, %v, query=%v, limit=%v)", fromUTC, toUTC, query, limit) +func (l *FileLog) SearchEvents(fromUTC, toUTC time.Time, namespace string, eventTypes []string, limit int, startKey string) ([]EventFields, string, error) { + l.Debugf("SearchEvents(%v, %v, namespace=%v, eventType=%v, limit=%v)", fromUTC, toUTC, namespace, eventTypes, limit) if limit <= 0 { limit = defaults.EventsIterationLimit } if limit > defaults.EventsMaxIterationLimit { - return nil, trace.BadParameter("limit %v exceeds max iteration limit %v", limit, defaults.MaxIterationLimit) + return nil, "", trace.BadParameter("limit %v exceeds max iteration limit %v", limit, defaults.MaxIterationLimit) } // how many days of logs to search? days := int(toUTC.Sub(fromUTC).Hours() / 24) if days < 0 { - return nil, trace.BadParameter("invalid days") - } - queryVals, err := url.ParseQuery(query) - if err != nil { - return nil, trace.BadParameter("invalid query") + return nil, "", trace.BadParameter("invalid days") } filtered, err := l.matchingFiles(fromUTC, toUTC) if err != nil { - return nil, trace.Wrap(err) + return nil, "", trace.Wrap(err) } + foundStart := startKey == "" var total int + var lastKey string // search within each file: events := make([]EventFields, 0) for i := range filtered { - found, err := l.findInFile(filtered[i].path, queryVals, &total, limit) + var found []EventFields + found, lastKey, foundStart, err = l.findInFile(filtered[i].path, eventTypes, &total, limit, startKey, foundStart) if err != nil { - return nil, trace.Wrap(err) + return nil, "", trace.Wrap(err) } events = append(events, found...) if limit > 0 && total >= limit { @@ -236,27 +232,22 @@ func (l *FileLog) SearchEvents(fromUTC, toUTC time.Time, query string, limit int // sure that events are not displayed out of order in case of multiple // auth servers. sort.Sort(ByTimeAndIndex(events)) - return events, nil + return events, lastKey, nil } -// SearchSessionEvents searches for session related events. Used to find completed sessions. -func (l *FileLog) SearchSessionEvents(fromUTC, toUTC time.Time, limit int) ([]EventFields, error) { +func (l *FileLog) SearchSessionEvents(fromUTC, toUTC time.Time, limit int, startKey string) ([]EventFields, string, error) { l.Debugf("SearchSessionEvents(%v, %v, %v)", fromUTC, toUTC, limit) // only search for specific event types - query := url.Values{} - query[EventType] = []string{ - SessionStartEvent, - SessionEndEvent, - } + eventTypes := []string{SessionStartEvent, SessionEndEvent} // because of the limit and distributed nature of auth server event // logs, some events can be fetched with session end event and without // session start event. to fix this, the code below filters out the events without // start event to guarantee that all events in the range will get fetched - events, err := l.SearchEvents(fromUTC, toUTC, query.Encode(), limit) + events, lastKey, err := l.SearchEvents(fromUTC, toUTC, "default", eventTypes, limit, startKey) if err != nil { - return nil, trace.Wrap(err) + return nil, lastKey, trace.Wrap(err) } // filter out 'session end' events that do not @@ -281,7 +272,7 @@ func (l *FileLog) SearchSessionEvents(fromUTC, toUTC time.Time, limit int) ([]Ev } } - return filtered, nil + return filtered, lastKey, nil } // Close closes the audit log, which inluces closing all file handles and releasing @@ -474,22 +465,16 @@ func parseFileTime(filename string) (time.Time, error) { // findInFile scans a given log file and returns events that fit the criteria // This simplistic implementation ONLY SEARCHES FOR EVENT TYPE(s) -// -// You can pass multiple types like "event=session.start&event=session.end" -func (l *FileLog) findInFile(fn string, query url.Values, total *int, limit int) ([]EventFields, error) { - l.Debugf("Called findInFile(%s, %v).", fn, query) +func (l *FileLog) findInFile(fn string, eventFilter []string, total *int, limit int, startKey string, foundStart bool) ([]EventFields, string, bool, error) { + l.Debugf("Called findInFile(%s, %v).", fn, eventFilter) retval := make([]EventFields, 0) - - eventFilter, ok := query[EventType] - if !ok && len(query) > 0 { - return nil, nil - } + var lastKey string doFilter := len(eventFilter) > 0 // open the log file: lf, err := os.OpenFile(fn, os.O_RDONLY, 0) if err != nil { - return nil, trace.Wrap(err) + return nil, "", false, trace.Wrap(err) } defer lf.Close() @@ -521,15 +506,22 @@ func (l *FileLog) findInFile(fn string, query url.Values, total *int, limit int) break } } - if accepted || !doFilter { + + id := ef.GetString(EventID) + if id == startKey { + foundStart = true + } + + if (accepted || !doFilter) && foundStart { retval = append(retval, ef) + lastKey = id *total++ if limit > 0 && *total >= limit { break } } } - return retval, nil + return retval, lastKey, foundStart, nil } type eventFile struct { diff --git a/lib/events/firestoreevents/firestoreevents.go b/lib/events/firestoreevents/firestoreevents.go index 832ed8db8f06f..a1b8d66a68dc5 100644 --- a/lib/events/firestoreevents/firestoreevents.go +++ b/lib/events/firestoreevents/firestoreevents.go @@ -19,6 +19,7 @@ package firestoreevents import ( "context" "encoding/json" + "fmt" "net/url" "sort" "strconv" @@ -458,64 +459,66 @@ func (l *Log) GetSessionEvents(namespace string, sid session.ID, after int, inlc return values, nil } -// SearchEvents is a flexible way to find The format of a query string -// depends on the implementing backend. A recommended format is urlencoded -// (good enough for Lucene/Solr) -// -// Pagination is also defined via backend-specific query format. -// -// The only mandatory requirement is a date range (UTC). Results must always -// show up sorted by date (newest first) -func (l *Log) SearchEvents(fromUTC, toUTC time.Time, filter string, limit int) ([]events.EventFields, error) { - g := l.WithFields(log.Fields{"From": fromUTC, "To": toUTC, "Filter": filter, "Limit": limit}) - filterVals, err := url.ParseQuery(filter) - if err != nil { - return nil, trace.BadParameter("missing or invalid parameter query in %q: %v", filter, err) - } - eventFilter, ok := filterVals[events.EventType] - if !ok && len(filterVals) > 0 { - return nil, nil - } - doFilter := len(eventFilter) > 0 +func (l *Log) SearchEvents(fromUTC, toUTC time.Time, namespace string, eventTypes []string, limit int, startKey string) ([]events.EventFields, string, error) { + g := l.WithFields(log.Fields{"From": fromUTC, "To": toUTC, "Namespace": namespace, "EventTypes": eventTypes, "Limit": limit, "StartKey": startKey}) + doFilter := len(eventTypes) > 0 + var lastKey int64 var values []events.EventFields + var parsedStartKey int64 = -1 + var err error + + if startKey != "" { + parsedStartKey, err = strconv.ParseInt(startKey, 10, 64) + if err != nil { + return nil, "", trace.WrapWithMessage(err, "failed to parse startKey, expected integer but found: %q", startKey) + } + } + + modifyquery := func(query firestore.Query) firestore.Query { + if startKey != "" { + return query.StartAt(parsedStartKey) + } + + return query + } start := time.Now() - docSnaps, err := l.svc.Collection(l.CollectionName). + docSnaps, err := modifyquery(l.svc.Collection(l.CollectionName). Where(eventNamespaceDocProperty, "==", defaults.Namespace). Where(createdAtDocProperty, ">=", fromUTC.Unix()). Where(createdAtDocProperty, "<=", toUTC.Unix()). - OrderBy(createdAtDocProperty, firestore.Asc). + OrderBy(createdAtDocProperty, firestore.Asc)). Limit(limit). Documents(l.svcContext).GetAll() batchReadLatencies.Observe(time.Since(start).Seconds()) batchReadRequests.Inc() if err != nil { - return nil, firestorebk.ConvertGRPCError(err) + return nil, "", firestorebk.ConvertGRPCError(err) } g.WithFields(log.Fields{"duration": time.Since(start)}).Debugf("Query completed.") for _, docSnap := range docSnaps { - var e event err = docSnap.DataTo(&e) if err != nil { - return nil, firestorebk.ConvertGRPCError(err) + return nil, "", firestorebk.ConvertGRPCError(err) } var fields events.EventFields data := []byte(e.Fields) if err := json.Unmarshal(data, &fields); err != nil { - return nil, trace.Errorf("failed to unmarshal event %v", err) + return nil, "", trace.Errorf("failed to unmarshal event %v", err) } var accepted bool - for i := range eventFilter { - if fields.GetString(events.EventType) == eventFilter[i] { + for i := range eventTypes { + if fields.GetString(events.EventType) == eventTypes[i] { accepted = true break } } if accepted || !doFilter { + lastKey = docSnap.Data()["createdAt"].(int64) values = append(values, fields) if limit > 0 && len(values) >= limit { break @@ -523,19 +526,18 @@ func (l *Log) SearchEvents(fromUTC, toUTC time.Time, filter string, limit int) ( } } sort.Sort(events.ByTimeAndIndex(values)) - return values, nil + return values, fmt.Sprintf("%d", lastKey), nil } // SearchSessionEvents returns session related events only. This is used to // find completed session. -func (l *Log) SearchSessionEvents(fromUTC time.Time, toUTC time.Time, limit int) ([]events.EventFields, error) { +func (l *Log) SearchSessionEvents(fromUTC time.Time, toUTC time.Time, limit int, startKey string) ([]events.EventFields, string, error) { // only search for specific event types - query := url.Values{} - query[events.EventType] = []string{ + query := []string{ events.SessionStartEvent, events.SessionEndEvent, } - return l.SearchEvents(fromUTC, toUTC, query.Encode(), limit) + return l.SearchEvents(fromUTC, toUTC, defaults.Namespace, query, limit, startKey) } // WaitForDelivery waits for resources to be released and outstanding requests to diff --git a/lib/events/forward.go b/lib/events/forward.go index 9a1fe193edc2d..acb87b0373f66 100644 --- a/lib/events/forward.go +++ b/lib/events/forward.go @@ -235,22 +235,21 @@ func (l *Forwarder) GetSessionEvents(namespace string, sid session.ID, after int return l.ForwardTo.GetSessionEvents(namespace, sid, after, includePrintEvents) } -// SearchEvents is a flexible way to find The format of a query string -// depends on the implementing backend. A recommended format is urlencoded -// (good enough for Lucene/Solr) +// SearchEvents is a flexible way to find events. // -// Pagination is also defined via backend-specific query format. +// Event types to filter can be specified and pagination is handled by an iterator key that allows +// a query to be resumed. // // The only mandatory requirement is a date range (UTC). Results must always // show up sorted by date (newest first) -func (l *Forwarder) SearchEvents(fromUTC, toUTC time.Time, query string, limit int) ([]EventFields, error) { - return l.ForwardTo.SearchEvents(fromUTC, toUTC, query, limit) +func (l *Forwarder) SearchEvents(fromUTC, toUTC time.Time, namespace string, eventType []string, limit int, startKey string) ([]EventFields, string, error) { + return l.ForwardTo.SearchEvents(fromUTC, toUTC, namespace, eventType, limit, startKey) } // SearchSessionEvents returns session related events only. This is used to // find completed session. -func (l *Forwarder) SearchSessionEvents(fromUTC time.Time, toUTC time.Time, limit int) ([]EventFields, error) { - return l.ForwardTo.SearchSessionEvents(fromUTC, toUTC, limit) +func (l *Forwarder) SearchSessionEvents(fromUTC time.Time, toUTC time.Time, limit int, startKey string) ([]EventFields, string, error) { + return l.ForwardTo.SearchSessionEvents(fromUTC, toUTC, limit, startKey) } // WaitForDelivery waits for resources to be released and outstanding requests to diff --git a/lib/events/multilog.go b/lib/events/multilog.go index 190f7fd44dc24..e681ce03a77d6 100644 --- a/lib/events/multilog.go +++ b/lib/events/multilog.go @@ -126,32 +126,35 @@ func (m *MultiLog) GetSessionEvents(namespace string, sid session.ID, after int, return events, err } -// SearchEvents is a flexible way to find events. The format of a query string -// depends on the implementing backend. A recommended format is urlencoded -// (good enough for Lucene/Solr) +// SearchEvents is a flexible way to find events. // -// Pagination is also defined via backend-specific query format. +// Event types to filter can be specified and pagination is handled by an iterator key that allows +// a query to be resumed. // // The only mandatory requirement is a date range (UTC). Results must always // show up sorted by date (newest first) -func (m *MultiLog) SearchEvents(fromUTC, toUTC time.Time, query string, limit int) (events []EventFields, err error) { +func (m *MultiLog) SearchEvents(fromUTC, toUTC time.Time, namespace string, eventTypes []string, limit int, startKey string) (events []EventFields, lastKey string, err error) { for _, log := range m.loggers { - events, err = log.SearchEvents(fromUTC, toUTC, query, limit) + events, lastKey, err := log.SearchEvents(fromUTC, toUTC, namespace, eventTypes, limit, startKey) if !trace.IsNotImplemented(err) { - return events, err + return events, lastKey, err } } - return events, err + return events, lastKey, err } -// SearchSessionEvents returns session related events only. This is used to -// find completed session. -func (m *MultiLog) SearchSessionEvents(fromUTC, toUTC time.Time, limit int) (events []EventFields, err error) { +// SearchSessionEvents is a flexible way to find session events. +// Only session events are returned by this function. +// This is used to find completed session. +// +// Event types to filter can be specified and pagination is handled by an iterator key that allows +// a query to be resumed. +func (m *MultiLog) SearchSessionEvents(fromUTC, toUTC time.Time, limit int, startKey string) (events []EventFields, lastKey string, err error) { for _, log := range m.loggers { - events, err = log.SearchSessionEvents(fromUTC, toUTC, limit) + events, lastKey, err = log.SearchSessionEvents(fromUTC, toUTC, limit, startKey) if !trace.IsNotImplemented(err) { - return events, err + return events, lastKey, err } } - return events, err + return events, lastKey, err } diff --git a/lib/events/test/suite.go b/lib/events/test/suite.go index 9ae3c9f5625f3..199c686ced2a1 100644 --- a/lib/events/test/suite.go +++ b/lib/events/test/suite.go @@ -79,6 +79,38 @@ type EventsSuite struct { QueryDelay time.Duration } +// EventPagination covers event search pagination. +func (s *EventsSuite) EventPagination(c *check.C) { + // 2019-05-10 14:43:0 GMT + // This serves no special purpose except to make querying easier. + baseTime := time.Unix(1557499380, 0) + + names := []string{"bob", "jack", "daisy", "evan"} + + for i, name := range names { + err := s.Log.EmitAuditEventLegacy(events.UserLocalLoginE, events.EventFields{ + events.LoginMethod: events.LoginMethodSAML, + events.AuthAttemptSuccess: true, + events.EventUser: name, + events.EventTime: baseTime.Add(time.Second * time.Duration(i)), + }) + c.Assert(err, check.IsNil) + } + + toTime := baseTime.Add(time.Hour) + var arr []events.EventFields + var err error + var checkpoint string + + for _, name := range names { + arr, checkpoint, err = s.Log.SearchEvents(baseTime, toTime, defaults.Namespace, nil, 1, checkpoint) + c.Assert(err, check.IsNil) + c.Assert(arr, check.HasLen, 1) + eventName := arr[0].GetString(events.EventUser) + c.Assert(name, check.Equals, eventName) + } +} + // SessionEventsCRUD covers session events func (s *EventsSuite) SessionEventsCRUD(c *check.C) { // Bob has logged in @@ -95,7 +127,7 @@ func (s *EventsSuite) SessionEventsCRUD(c *check.C) { time.Sleep(s.QueryDelay) } - history, err := s.Log.SearchEvents(s.Clock.Now().Add(-1*time.Hour), s.Clock.Now().Add(time.Hour), "", 100) + history, _, err := s.Log.SearchEvents(s.Clock.Now().Add(-1*time.Hour), s.Clock.Now().Add(time.Hour), defaults.Namespace, nil, 100, "") c.Assert(err, check.IsNil) c.Assert(history, check.HasLen, 1) @@ -131,11 +163,11 @@ func (s *EventsSuite) SessionEventsCRUD(c *check.C) { c.Assert(history[0].GetString(events.EventType), check.Equals, events.SessionStartEvent) c.Assert(history[1].GetString(events.EventType), check.Equals, events.SessionEndEvent) - history, err = s.Log.SearchSessionEvents(s.Clock.Now().Add(-1*time.Hour), s.Clock.Now().Add(2*time.Hour), 100) + history, _, err = s.Log.SearchSessionEvents(s.Clock.Now().Add(-1*time.Hour), s.Clock.Now().Add(2*time.Hour), 100, "") c.Assert(err, check.IsNil) c.Assert(history, check.HasLen, 2) - history, err = s.Log.SearchSessionEvents(s.Clock.Now().Add(-1*time.Hour), s.Clock.Now().Add(time.Hour-time.Second), 100) + history, _, err = s.Log.SearchSessionEvents(s.Clock.Now().Add(-1*time.Hour), s.Clock.Now().Add(time.Hour-time.Second), 100, "") c.Assert(err, check.IsNil) c.Assert(history, check.HasLen, 1) } diff --git a/lib/events/writer.go b/lib/events/writer.go index a855615625ad2..12eae970412ac 100644 --- a/lib/events/writer.go +++ b/lib/events/writer.go @@ -103,23 +103,25 @@ func (w *WriterLog) GetSessionEvents(namespace string, sid session.ID, after int return nil, trace.NotImplemented("not implemented") } -// SearchEvents is a flexible way to find events. The format of a query string -// depends on the implementing backend. A recommended format is urlencoded -// (good enough for Lucene/Solr) +// SearchEvents is a flexible way to find events. // -// Pagination is also defined via backend-specific query format. +// Event types to filter can be specified and pagination is handled by an iterator key that allows +// a query to be resumed. // // The only mandatory requirement is a date range (UTC). Results must always // show up sorted by date (newest first) -func (w *WriterLog) SearchEvents(fromUTC, toUTC time.Time, query string, limit int) ([]EventFields, error) { - return nil, trace.NotImplemented("not implemented") +func (w *WriterLog) SearchEvents(fromUTC, toUTC time.Time, namespace string, eventTypes []string, limit int, startKey string) (events []EventFields, lastKey string, err error) { + return nil, "", trace.NotImplemented("not implemented") } -// SearchSessionEvents returns session related events only. This is used to -// find completed session. -func (w *WriterLog) SearchSessionEvents(fromUTC time.Time, toUTC time.Time, limit int) ([]EventFields, error) { - return nil, trace.NotImplemented("not implemented") - +// SearchSessionEvents is a flexible way to find session events. +// Only session events are returned by this function. +// This is used to find completed session. +// +// Event types to filter can be specified and pagination is handled by an iterator key that allows +// a query to be resumed. +func (w *WriterLog) SearchSessionEvents(fromUTC, toUTC time.Time, limit int, startKey string) (events []EventFields, lastKey string, err error) { + return nil, "", trace.NotImplemented("not implemented") } // WaitForDelivery waits for resources to be released and outstanding requests to diff --git a/lib/srv/exec_test.go b/lib/srv/exec_test.go index 2b10792e37cd5..079875ede9a75 100644 --- a/lib/srv/exec_test.go +++ b/lib/srv/exec_test.go @@ -539,12 +539,12 @@ func (a *fakeLog) GetSessionEvents(namespace string, sid rsession.ID, after int, return nil, trace.NotFound("") } -func (a *fakeLog) SearchEvents(fromUTC, toUTC time.Time, query string, limit int) ([]events.EventFields, error) { - return nil, trace.NotFound("") +func (a *fakeLog) SearchEvents(fromUTC, toUTC time.Time, namespace string, eventTypes []string, limit int, startKey string) ([]events.EventFields, string, error) { + return nil, "", trace.NotFound("") } -func (a *fakeLog) SearchSessionEvents(fromUTC time.Time, toUTC time.Time, limit int) ([]events.EventFields, error) { - return nil, trace.NotFound("") +func (a *fakeLog) SearchSessionEvents(fromUTC time.Time, toUTC time.Time, limit int, startKey string) ([]events.EventFields, string, error) { + return nil, "", trace.NotFound("") } func (a *fakeLog) WaitForDelivery(context.Context) error { diff --git a/lib/web/apiserver.go b/lib/web/apiserver.go index 5cab4741bde25..951b67b2b4f5e 100644 --- a/lib/web/apiserver.go +++ b/lib/web/apiserver.go @@ -1904,7 +1904,7 @@ func (h *Handler) clusterSearchSessionEvents(w http.ResponseWriter, r *http.Requ } } - el, err := clt.SearchSessionEvents(from, to, defaults.EventsIterationLimit) + el, _, err := clt.SearchSessionEvents(from, to, defaults.EventsIterationLimit, "") if err != nil { return nil, trace.Wrap(err) } @@ -1936,15 +1936,17 @@ func (h *Handler) clusterSearchEvents(w http.ResponseWriter, r *http.Request, p if err != nil { return nil, trace.Wrap(err) } - query := url.Values{} - if include := values.Get("include"); include != "" { - query[events.EventType] = strings.Split(include, ";") - } clt, err := ctx.GetUserClient(site) if err != nil { return nil, trace.Wrap(err) } - fields, err := clt.SearchEvents(from, to, query.Encode(), limit) + + var eventTypes []string + if include := values.Get("include"); include != "" { + eventTypes = strings.Split(include, ";") + } + + fields, _, err := clt.SearchEvents(from, to, defaults.Namespace, eventTypes, limit, "") if err != nil { return nil, trace.Wrap(err) } diff --git a/rfd/0019-event-iteration-api.md b/rfd/0019-event-iteration-api.md index 9ddc12fc0e6b0..3b2f86fade67e 100644 --- a/rfd/0019-event-iteration-api.md +++ b/rfd/0019-event-iteration-api.md @@ -1,6 +1,6 @@ --- authors: Joel Wejdenstal (jwejdenstal@goteleport.com) -state: draft +state: implemented --- # RFD 19 - Event Fetch API with Pagination From 63eb715be841db5518027df42e03f7df5a72ff0d Mon Sep 17 00:00:00 2001 From: xacrimon Date: Thu, 13 May 2021 01:08:14 +0200 Subject: [PATCH 02/28] mostly refactor internal handlers minus helpers to grpc events --- api/client/client.go | 25 +- api/client/proto/authservice.pb.go | 235 +++++++++--------- api/client/proto/authservice.proto | 2 +- e | 2 +- lib/auth/auth_with_roles.go | 4 +- lib/auth/clt.go | 24 +- lib/auth/grpcserver.go | 27 +- lib/events/api.go | 4 +- lib/events/auditlog.go | 8 +- lib/events/discard.go | 8 +- lib/events/dynamic.go | 29 +++ lib/events/dynamoevents/dynamoevents.go | 15 +- lib/events/filelog.go | 26 +- lib/events/firestoreevents/firestoreevents.go | 16 +- lib/events/forward.go | 4 +- lib/events/multilog.go | 4 +- lib/events/writer.go | 4 +- lib/web/apiserver.go | 31 ++- 18 files changed, 282 insertions(+), 186 deletions(-) create mode 100644 lib/events/dynamic.go diff --git a/api/client/client.go b/api/client/client.go index 3d8cc6eb84776..7b5959015c57b 100644 --- a/api/client/client.go +++ b/api/client/client.go @@ -20,7 +20,6 @@ import ( "compress/gzip" "context" "crypto/tls" - "encoding/json" "io" "net" "sync" @@ -1295,7 +1294,7 @@ func (c *Client) DeleteAllNodes(ctx context.Context, namespace string) error { } // SearchEvents allows searching for events with a full pagination support. -func (c *Client) SearchEvents(ctx context.Context, fromUTC, toUTC time.Time, namespace string, eventTypes []string, limit int, startKey string) ([]map[string]interface{}, string, error) { +func (c *Client) SearchEvents(ctx context.Context, fromUTC, toUTC time.Time, namespace string, eventTypes []string, limit int, startKey string) ([]events.AuditEvent, string, error) { request := &proto.GetEventsRequest{ Namespace: namespace, StartDate: fromUTC, @@ -1310,16 +1309,20 @@ func (c *Client) SearchEvents(ctx context.Context, fromUTC, toUTC time.Time, nam return nil, "", trail.FromGRPC(err) } - var decodedEvents []map[string]interface{} - if err := json.Unmarshal(response.Items, &decodedEvents); err != nil { - return nil, "", trace.Wrap(err) + decodedEvents := make([]events.AuditEvent, len(response.Items)) + for _, rawEvent := range response.Items { + event, err := events.FromOneOf(*rawEvent) + if err != nil { + return nil, "", trace.Wrap(err) + } + decodedEvents = append(decodedEvents, event) } return decodedEvents, response.LastKey, nil } // SearchSessionEvents allows searching for session events with a full pagination support. -func (c *Client) SearchSessionEvents(ctx context.Context, fromUTC time.Time, toUTC time.Time, limit int, startKey string) ([]map[string]interface{}, string, error) { +func (c *Client) SearchSessionEvents(ctx context.Context, fromUTC time.Time, toUTC time.Time, limit int, startKey string) ([]events.AuditEvent, string, error) { request := &proto.GetSessionEventsRequest{ StartDate: fromUTC, EndDate: toUTC, @@ -1332,9 +1335,13 @@ func (c *Client) SearchSessionEvents(ctx context.Context, fromUTC time.Time, toU return nil, "", trail.FromGRPC(err) } - var decodedEvents []map[string]interface{} - if err := json.Unmarshal(response.Items, &decodedEvents); err != nil { - return nil, "", trace.Wrap(err) + decodedEvents := make([]events.AuditEvent, len(response.Items)) + for _, rawEvent := range response.Items { + event, err := events.FromOneOf(*rawEvent) + if err != nil { + return nil, "", trace.Wrap(err) + } + decodedEvents = append(decodedEvents, event) } return decodedEvents, response.LastKey, nil diff --git a/api/client/proto/authservice.pb.go b/api/client/proto/authservice.pb.go index 0e476434a7116..0138cf7b7914a 100644 --- a/api/client/proto/authservice.pb.go +++ b/api/client/proto/authservice.pb.go @@ -3594,7 +3594,7 @@ func (m *DatabaseCSRRequest) GetCSR() []byte { if m != nil { return m.CSR } - return nil + return nil } func (m *DatabaseCSRRequest) GetClusterName() string { @@ -6060,7 +6060,7 @@ func (m *GetSessionEventsRequest) GetStartKey() string { } type Events struct { - Items []byte `protobuf:"bytes,1,opt,name=Items,proto3" json:"Items,omitempty"` + Items []*events.OneOf `protobuf:"bytes,1,rep,name=Items,proto3" json:"Items,omitempty"` // the key of the last event if the returned set did not contain all events found i.e limit < // actual amount. this is the key clients can supply in another API request to continue fetching // events from the previous last position @@ -6103,7 +6103,7 @@ func (m *Events) XXX_DiscardUnknown() { var xxx_messageInfo_Events proto.InternalMessageInfo -func (m *Events) GetItems() []byte { +func (m *Events) GetItems() []*events.OneOf { if m != nil { return m.Items } @@ -6219,7 +6219,7 @@ func init() { func init() { proto.RegisterFile("authservice.proto", fileDescriptor_ce8bd90b12161215) } var fileDescriptor_ce8bd90b12161215 = []byte{ - // 5476 bytes of a gzipped FileDescriptorProto + // 5478 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x7c, 0x4b, 0x73, 0x23, 0xc9, 0x71, 0x30, 0x1b, 0x7c, 0x01, 0xc9, 0x17, 0x58, 0x7c, 0x61, 0x30, 0x1c, 0x62, 0xd4, 0x23, 0xad, 0x46, 0xab, 0x15, 0xb9, 0x02, 0x77, 0xbf, 0x5d, 0xed, 0xec, 0xee, 0x7c, 0x00, 0xf8, 0x00, 0x76, @@ -6471,98 +6471,98 @@ var fileDescriptor_ce8bd90b12161215 = []byte{ 0xcc, 0x18, 0x12, 0x84, 0x9a, 0xd9, 0xba, 0xb4, 0xd8, 0x4b, 0xd0, 0xb4, 0xc1, 0x3e, 0x88, 0x35, 0xa9, 0x08, 0x8f, 0xf0, 0x4b, 0x5e, 0xc2, 0x04, 0xdf, 0xfa, 0x1f, 0xe8, 0x25, 0xbb, 0xcf, 0xaf, 0xdd, 0x54, 0x7b, 0x28, 0x1a, 0x6b, 0x6f, 0xac, 0x71, 0xea, 0x75, 0x34, 0x0e, 0x34, 0x9a, 0x4c, - 0xd2, 0x68, 0x2a, 0xa2, 0xd1, 0xc7, 0x30, 0xc3, 0xd4, 0x20, 0xb4, 0x35, 0x1f, 0x5f, 0xb2, 0xb7, - 0xe0, 0x79, 0x83, 0x7d, 0x90, 0x7a, 0xee, 0xc8, 0xf4, 0x28, 0x29, 0x73, 0x46, 0xf1, 0xf9, 0xee, - 0xbb, 0x90, 0x09, 0x7e, 0x3d, 0x82, 0xe4, 0xc6, 0xda, 0x93, 0x5a, 0x83, 0xe5, 0xc6, 0xe3, 0x93, - 0x46, 0x56, 0x43, 0x00, 0x33, 0x7b, 0xfb, 0x47, 0xfb, 0x8d, 0xfd, 0x6c, 0xaa, 0xf8, 0xfb, 0x0f, - 0x61, 0x8e, 0x04, 0x02, 0x7e, 0x09, 0x86, 0x3e, 0x85, 0xc5, 0x3a, 0xee, 0xb5, 0x1f, 0x61, 0xec, - 0x94, 0xba, 0xd6, 0x15, 0xf6, 0x90, 0x88, 0x8e, 0x01, 0x28, 0xbf, 0x3e, 0xa2, 0xfa, 0xfe, 0xa5, - 0xe3, 0xbf, 0xbc, 0xaf, 0xa1, 0x1f, 0xc3, 0x1c, 0xed, 0x03, 0xe5, 0x82, 0xcf, 0xcb, 0xbd, 0xa1, - 0x79, 0xf1, 0x45, 0x07, 0xdf, 0xd7, 0xd0, 0x67, 0x30, 0x7b, 0x88, 0x7d, 0xba, 0xad, 0xbe, 0x17, - 0xf9, 0x8d, 0x8c, 0x5a, 0x2f, 0xf0, 0x57, 0xbe, 0x88, 0xf9, 0xe8, 0xa5, 0x20, 0xaa, 0x40, 0x9a, - 0x93, 0x7b, 0x48, 0x8f, 0xd0, 0x7b, 0x31, 0x0c, 0x56, 0x22, 0x0c, 0x8e, 0x2c, 0xcf, 0x47, 0x3b, - 0x00, 0xec, 0x16, 0x8c, 0x8a, 0x11, 0x9d, 0x23, 0x3f, 0xa2, 0x3b, 0x3a, 0x24, 0xb9, 0x8a, 0xe4, - 0xa2, 0x9b, 0xca, 0x9d, 0x60, 0x2c, 0x74, 0x04, 0x8b, 0xc1, 0x9d, 0xd4, 0xcd, 0x95, 0x48, 0xe2, - 0xf6, 0x09, 0x2c, 0x8b, 0x67, 0x99, 0x20, 0xfd, 0xa0, 0xa4, 0x84, 0x14, 0xac, 0x04, 0x43, 0xc3, - 0x90, 0x97, 0x69, 0xd5, 0x74, 0x82, 0xee, 0x4a, 0x4c, 0x62, 0xb3, 0x60, 0xfe, 0x7b, 0x63, 0x30, - 0x58, 0x1c, 0xbc, 0xaf, 0xbd, 0xaf, 0xa1, 0x2f, 0x60, 0x41, 0x09, 0x92, 0x48, 0x94, 0xad, 0x71, - 0x19, 0x22, 0xbf, 0x19, 0x3f, 0xc8, 0xe3, 0xea, 0x01, 0x51, 0xd7, 0x8f, 0xb4, 0x76, 0xe5, 0xe3, - 0x5a, 0xb8, 0x58, 0xb7, 0x70, 0x5e, 0xbc, 0x69, 0x47, 0x48, 0xf6, 0x61, 0x85, 0xbf, 0x75, 0x28, - 0xdd, 0xfe, 0x09, 0xcd, 0x60, 0x89, 0xd6, 0x7f, 0x08, 0x2b, 0x7c, 0x2d, 0x15, 0x36, 0xd9, 0xe0, - 0x15, 0x9e, 0xf7, 0x0d, 0x25, 0x32, 0xf8, 0x02, 0xd6, 0xea, 0x11, 0x7d, 0x58, 0x77, 0xd6, 0x2d, - 0x95, 0x85, 0xd4, 0x06, 0x96, 0xc8, 0xeb, 0x11, 0xa0, 0x7a, 0xff, 0xec, 0xd2, 0x0a, 0xd8, 0x5d, - 0x59, 0xf8, 0x05, 0xba, 0x13, 0x51, 0x89, 0x00, 0x29, 0x1a, 0x8d, 0x97, 0xf9, 0x04, 0x8d, 0x51, - 0x83, 0xbd, 0xb5, 0xb1, 0x26, 0x0e, 0xd3, 0x31, 0xcf, 0xac, 0xae, 0xe5, 0x5b, 0x98, 0xb8, 0x85, - 0x4c, 0x20, 0x0f, 0x89, 0x15, 0xbc, 0x95, 0x88, 0x81, 0x3e, 0x87, 0x85, 0x43, 0xec, 0x87, 0x9d, - 0x6e, 0x68, 0x63, 0xa4, 0x37, 0x8e, 0xaf, 0x9b, 0xb8, 0xe5, 0x52, 0xdb, 0xeb, 0x6a, 0x90, 0x3d, - 0x71, 0xda, 0xa6, 0x8f, 0x25, 0x16, 0x77, 0x46, 0x58, 0x70, 0x14, 0xd3, 0x35, 0x2f, 0xbd, 0x44, - 0x6b, 0xed, 0xc0, 0xd4, 0xb1, 0xd5, 0xeb, 0x20, 0x71, 0x15, 0x26, 0xf5, 0x28, 0xe5, 0x57, 0x14, - 0x18, 0x77, 0x3d, 0x1f, 0x0a, 0xd7, 0xb4, 0x78, 0xa1, 0x9f, 0x04, 0xb5, 0xc9, 0x4d, 0x5a, 0xc1, - 0xf2, 0xd2, 0xbe, 0x8f, 0x47, 0x6c, 0xee, 0xa2, 0x9f, 0xd3, 0x75, 0x18, 0xc5, 0x40, 0xf7, 0xf8, - 0x5c, 0xe3, 0xfa, 0xc4, 0xf2, 0xb7, 0x13, 0x67, 0x68, 0xee, 0xa2, 0x53, 0xf1, 0xde, 0x17, 0xc3, - 0xfd, 0x1d, 0xa5, 0x05, 0xe5, 0x35, 0x27, 0xd8, 0xa1, 0x71, 0x9e, 0xfe, 0xc6, 0xd4, 0x5a, 0x28, - 0xad, 0xd4, 0x86, 0x93, 0x57, 0x7f, 0xe7, 0x0a, 0xed, 0xd2, 0xc8, 0x4e, 0x9b, 0x3f, 0xd1, 0xba, - 0x4a, 0xe1, 0xc5, 0x93, 0xbc, 0xaf, 0xa1, 0x5d, 0x00, 0x26, 0x25, 0x9d, 0x48, 0x1d, 0x4e, 0x5c, - 0xfd, 0x5d, 0x12, 0xfe, 0xdb, 0xaf, 0x48, 0xf4, 0xb9, 0x48, 0x01, 0x94, 0x28, 0xa7, 0x1c, 0x88, - 0x64, 0xad, 0x92, 0xe8, 0x6b, 0x90, 0x2d, 0xb5, 0x68, 0x40, 0x0b, 0x3a, 0x83, 0xd0, 0x56, 0xb0, - 0x59, 0xd4, 0x01, 0xc1, 0x6b, 0x2d, 0xda, 0x68, 0x74, 0x84, 0x49, 0x85, 0x5b, 0x85, 0x8d, 0x20, - 0x35, 0x45, 0x86, 0xe2, 0x29, 0x12, 0x85, 0xda, 0x87, 0xd5, 0x8a, 0xd9, 0x6b, 0xe1, 0xee, 0x9b, - 0xb1, 0xf9, 0x84, 0xee, 0x6c, 0xa9, 0x6b, 0x6a, 0x3d, 0x4a, 0xcf, 0x37, 0xb6, 0xe8, 0x36, 0x97, - 0x50, 0x4b, 0xb0, 0xc4, 0x8c, 0x18, 0x9a, 0x25, 0x89, 0x3a, 0x69, 0xfa, 0x8f, 0x60, 0x71, 0x9f, - 0x44, 0xbe, 0x7e, 0xdb, 0x62, 0x35, 0x31, 0x52, 0x9b, 0x88, 0x12, 0x09, 0xab, 0xb0, 0xcc, 0x13, - 0x41, 0xd8, 0x4e, 0x14, 0x04, 0xdf, 0xd1, 0x8e, 0xad, 0xfc, 0xaa, 0x60, 0x2b, 0x77, 0x1e, 0x89, - 0x34, 0xa7, 0x74, 0xcf, 0x04, 0x69, 0x2e, 0xae, 0xab, 0x27, 0x48, 0x73, 0xf1, 0x0d, 0x37, 0x65, - 0x58, 0x8a, 0x34, 0xce, 0xa0, 0x3b, 0x22, 0xd9, 0xc6, 0x36, 0xd4, 0xc4, 0x14, 0x2c, 0x55, 0x61, - 0xd5, 0x51, 0x1e, 0xf1, 0x5d, 0x34, 0x89, 0x36, 0x3a, 0x0e, 0xb2, 0x9c, 0xdc, 0x12, 0x83, 0xd4, - 0x1b, 0x81, 0xb8, 0x76, 0x99, 0x44, 0x8e, 0x75, 0x72, 0x78, 0x51, 0x9b, 0x49, 0xd0, 0x56, 0x60, - 0x91, 0xd8, 0x8e, 0x99, 0x7c, 0x21, 0x71, 0x9c, 0x1b, 0x4d, 0x5a, 0x00, 0xf6, 0x7b, 0x65, 0xd1, - 0x05, 0x90, 0x5b, 0x0f, 0x46, 0x16, 0x40, 0xed, 0x28, 0x38, 0xa4, 0xaf, 0x47, 0x52, 0x57, 0x08, - 0x4a, 0x50, 0x25, 0x7f, 0x27, 0x8e, 0x4f, 0xb8, 0x92, 0x75, 0xc8, 0x46, 0x9b, 0x2a, 0x02, 0x4d, - 0x13, 0xba, 0x43, 0x02, 0x4d, 0x13, 0xbb, 0x31, 0xbe, 0x80, 0x6c, 0xb4, 0xa3, 0x22, 0x60, 0x9a, - 0xd0, 0x6a, 0x91, 0xb8, 0x14, 0x07, 0xb0, 0xaa, 0x2e, 0xe0, 0x35, 0xfa, 0x26, 0x57, 0x32, 0x0b, - 0x4a, 0x1f, 0x05, 0x12, 0xa9, 0x21, 0xd2, 0xb2, 0x31, 0x62, 0xfd, 0x98, 0x7e, 0x0e, 0x66, 0x7d, - 0xa9, 0x27, 0xe3, 0x26, 0xd6, 0x8f, 0x6b, 0xe1, 0x08, 0x0c, 0x25, 0xc9, 0x25, 0x22, 0x6e, 0x74, - 0xe0, 0x55, 0x0c, 0x75, 0x13, 0xd1, 0x92, 0xf8, 0xec, 0xc1, 0x9c, 0xd4, 0xcc, 0x81, 0x6e, 0x29, - 0x66, 0x52, 0x3c, 0x3e, 0xaf, 0x28, 0xa7, 0x3a, 0x7b, 0x05, 0xe6, 0xe5, 0x96, 0x90, 0x44, 0x29, - 0x6e, 0x8f, 0xf2, 0xf0, 0xa4, 0x6a, 0x7a, 0x31, 0xb0, 0x02, 0x93, 0x66, 0x33, 0x6a, 0x1c, 0x45, - 0xa0, 0x64, 0x95, 0x90, 0x6c, 0x9a, 0x6b, 0x44, 0x4a, 0xce, 0x44, 0x2b, 0x2c, 0x27, 0xab, 0xbf, - 0xd1, 0x9a, 0xf0, 0x8b, 0xb1, 0x63, 0xa2, 0xd5, 0x52, 0xa4, 0x87, 0x05, 0x49, 0x5e, 0x12, 0xd3, - 0xf7, 0x91, 0xdf, 0x4a, 0x1a, 0xe6, 0x66, 0x3a, 0x82, 0xe5, 0x91, 0x16, 0x16, 0x54, 0x50, 0xe2, - 0xf1, 0x68, 0x2f, 0xca, 0x98, 0xf3, 0xdf, 0xf2, 0x48, 0xff, 0x4a, 0xc0, 0x2d, 0xa9, 0xb3, 0x25, - 0x91, 0x5b, 0x03, 0xd6, 0x62, 0x7b, 0x5a, 0x82, 0xfa, 0x70, 0x5c, 0xc7, 0x4b, 0x22, 0xd7, 0x9f, - 0x03, 0x1a, 0xed, 0x40, 0x09, 0x4e, 0x84, 0x89, 0x3d, 0x32, 0xc1, 0x89, 0x70, 0x4c, 0xfb, 0xca, - 0x11, 0xac, 0xc6, 0xb5, 0x9f, 0x20, 0x5d, 0xb1, 0x67, 0x6c, 0xfb, 0x48, 0x4c, 0x92, 0x33, 0xc4, - 0xa6, 0x4c, 0xe0, 0x36, 0xa6, 0x19, 0x25, 0x51, 0xf9, 0x5f, 0x88, 0x06, 0xa3, 0xd1, 0xa6, 0x91, - 0xa0, 0x2e, 0xbe, 0xa6, 0xab, 0x64, 0x4c, 0xb9, 0xb1, 0x54, 0xb7, 0x3a, 0x3d, 0xa9, 0xbf, 0x23, - 0x28, 0x36, 0x46, 0x1b, 0x4b, 0x82, 0x00, 0x10, 0xd7, 0x0e, 0xf2, 0x14, 0x56, 0x45, 0x26, 0x94, - 0xbb, 0x28, 0xd0, 0x08, 0x4d, 0xd8, 0xe4, 0x11, 0x04, 0x83, 0xd8, 0xb6, 0x0b, 0x56, 0xad, 0xd3, - 0x3f, 0x53, 0x20, 0x55, 0xeb, 0x52, 0x7b, 0x43, 0x5e, 0xed, 0x84, 0x40, 0x0f, 0x68, 0xb5, 0xce, - 0x7a, 0x50, 0x93, 0xf6, 0xfa, 0x86, 0xca, 0x29, 0x74, 0x83, 0x5d, 0x71, 0xff, 0x42, 0x27, 0x54, - 0x39, 0x5f, 0x5f, 0x80, 0x53, 0x22, 0xb5, 0x00, 0x97, 0x05, 0x4d, 0x3e, 0x21, 0xcf, 0xcb, 0x6f, - 0x43, 0x81, 0xad, 0x62, 0x5e, 0x9f, 0x02, 0x5b, 0xc5, 0x3d, 0x26, 0xd1, 0x7a, 0xaf, 0x21, 0xea, - 0xab, 0x90, 0xdf, 0x9d, 0xb1, 0xaf, 0x41, 0xf9, 0xad, 0xf1, 0x4f, 0x28, 0x52, 0x15, 0x19, 0x3e, - 0x5e, 0xc8, 0x45, 0xcc, 0xc8, 0x53, 0x87, 0x9c, 0x46, 0x63, 0xde, 0x3b, 0x1e, 0xd3, 0x2b, 0xe2, - 0xa7, 0xb5, 0xbd, 0x0a, 0xff, 0x73, 0x09, 0xb6, 0x3b, 0x72, 0x71, 0x25, 0xfd, 0xd2, 0x5c, 0x68, - 0x3d, 0x86, 0xa2, 0x10, 0x36, 0x8b, 0xa8, 0x4e, 0xef, 0x5e, 0x14, 0x68, 0xcc, 0xdd, 0x55, 0x0c, - 0xc3, 0x7c, 0x3c, 0x43, 0x7a, 0x0f, 0x47, 0x83, 0x3e, 0xf1, 0x03, 0x55, 0xcc, 0x04, 0x19, 0xc6, - 0xe5, 0x0e, 0x66, 0xd5, 0x78, 0x36, 0x42, 0xba, 0xeb, 0x1c, 0x84, 0x59, 0xac, 0x5e, 0x7a, 0x7c, - 0xf4, 0x5a, 0x16, 0x53, 0x08, 0x03, 0x8b, 0x29, 0xd0, 0x57, 0xb3, 0x58, 0x84, 0xa1, 0x6a, 0x31, - 0x55, 0xcc, 0x04, 0x19, 0xae, 0xb7, 0x58, 0x3c, 0x9b, 0x9b, 0x5a, 0xec, 0x4b, 0x9a, 0x29, 0x0e, - 0xe9, 0x2f, 0xec, 0xbd, 0x92, 0xcd, 0x72, 0xa2, 0xee, 0x51, 0x49, 0x9b, 0xbb, 0xe8, 0x19, 0x6d, - 0x9c, 0x8d, 0xc0, 0x6f, 0x66, 0xb7, 0xcd, 0x24, 0xa6, 0xd4, 0x72, 0x35, 0x58, 0x63, 0x96, 0x8b, - 0x8a, 0x9b, 0x28, 0x4b, 0xa2, 0xda, 0x87, 0x22, 0xed, 0x46, 0x59, 0xbd, 0xaa, 0xfd, 0xf6, 0xa8, - 0x8b, 0x34, 0x5c, 0x52, 0xd5, 0xb4, 0x47, 0x4b, 0x1e, 0x95, 0x89, 0xb8, 0x2d, 0x53, 0xd1, 0x9b, - 0x45, 0x54, 0xa3, 0xab, 0xa0, 0x82, 0xc7, 0xd5, 0x84, 0xf1, 0x6c, 0xa8, 0x91, 0xaa, 0x22, 0x3f, - 0x47, 0x64, 0x4a, 0x9a, 0x3b, 0x59, 0xa8, 0xa0, 0x60, 0xbe, 0xa1, 0x76, 0x49, 0x26, 0x62, 0x79, - 0x86, 0xd5, 0xa7, 0xd7, 0x59, 0x26, 0xfa, 0x87, 0x8c, 0xd0, 0xff, 0x87, 0x8c, 0x20, 0xbe, 0xde, - 0x20, 0x51, 0x6a, 0x6a, 0x90, 0xcf, 0x61, 0x8e, 0x1b, 0x84, 0x4a, 0x90, 0x34, 0x53, 0xa2, 0xf8, - 0x9f, 0xc1, 0x1c, 0x37, 0xc3, 0x58, 0x0d, 0x92, 0xc8, 0x9b, 0xb4, 0x19, 0x38, 0xe1, 0x0f, 0x8d, - 0x24, 0x6a, 0x74, 0xed, 0x9f, 0x39, 0x21, 0x7c, 0xeb, 0xc9, 0x7c, 0xaf, 0xa5, 0x1f, 0x73, 0xb3, - 0x96, 0x09, 0xde, 0x25, 0x91, 0x94, 0xfe, 0x95, 0x97, 0xb9, 0xfc, 0x82, 0xfc, 0x24, 0xe4, 0xa1, - 0x12, 0x8b, 0xbb, 0xf2, 0x1b, 0x9e, 0x74, 0x1f, 0x10, 0xfb, 0xb8, 0x17, 0x61, 0x51, 0xce, 0x7e, - 0xfb, 0x9f, 0x5b, 0xda, 0xb7, 0xdf, 0x6d, 0x69, 0xff, 0xfa, 0xdd, 0x96, 0xf6, 0xc7, 0xef, 0xb6, - 0xb4, 0xb3, 0x19, 0x3a, 0xbe, 0xfb, 0xdf, 0x01, 0x00, 0x00, 0xff, 0xff, 0x89, 0x4e, 0xb4, 0x3a, - 0x7f, 0x4d, 0x00, 0x00, + 0xd2, 0x68, 0x2a, 0xa2, 0xd1, 0x21, 0xcc, 0x30, 0x35, 0xc8, 0x49, 0xb4, 0xe6, 0xe3, 0xcb, 0xf0, + 0x24, 0x2a, 0xbf, 0x8b, 0x19, 0x6c, 0x8c, 0x94, 0x77, 0x47, 0xa6, 0x47, 0x39, 0x31, 0xdf, 0x14, + 0x9f, 0xef, 0xbe, 0x0b, 0x99, 0xe0, 0xb7, 0x25, 0x48, 0xaa, 0xac, 0x3d, 0xa9, 0x35, 0x58, 0xaa, + 0x3c, 0x3e, 0x69, 0x64, 0x35, 0x04, 0x30, 0xb3, 0xb7, 0x7f, 0xb4, 0xdf, 0xd8, 0xcf, 0xa6, 0x8a, + 0xbf, 0xff, 0x10, 0xe6, 0x48, 0x5c, 0xe0, 0x77, 0x62, 0xe8, 0x53, 0x58, 0xac, 0xe3, 0x5e, 0xfb, + 0x11, 0xc6, 0x4e, 0xa9, 0x6b, 0x5d, 0x61, 0x0f, 0x89, 0x60, 0x19, 0x80, 0xf2, 0xeb, 0x23, 0x96, + 0xd8, 0xbf, 0x74, 0xfc, 0x97, 0xf7, 0x35, 0xf4, 0x63, 0x98, 0xa3, 0x6d, 0xa1, 0x5c, 0x8f, 0x79, + 0xb9, 0x55, 0x34, 0x2f, 0xbe, 0xe8, 0xe0, 0xfb, 0x1a, 0xfa, 0x0c, 0x66, 0x0f, 0xb1, 0x4f, 0x77, + 0xd9, 0xf7, 0x22, 0xbf, 0xa0, 0x51, 0xeb, 0x05, 0xee, 0xcb, 0xd7, 0x34, 0x1f, 0xbd, 0x23, 0x44, + 0x15, 0x48, 0x73, 0x72, 0x0f, 0xe9, 0x11, 0x7a, 0x2f, 0x86, 0xc1, 0x4a, 0x84, 0xc1, 0x91, 0xe5, + 0xf9, 0x68, 0x07, 0x80, 0x5d, 0x8a, 0x51, 0x31, 0xa2, 0x73, 0xe4, 0x47, 0x74, 0x47, 0x87, 0x24, + 0x75, 0x91, 0xd4, 0x74, 0x53, 0xb9, 0x13, 0x8c, 0x85, 0x8e, 0x60, 0x31, 0xb8, 0xa2, 0xba, 0xb9, + 0x12, 0x49, 0xdc, 0x3e, 0x81, 0x65, 0xf1, 0x4a, 0x13, 0x64, 0x23, 0x94, 0x94, 0x9f, 0x82, 0x95, + 0x60, 0x68, 0x18, 0xf2, 0x32, 0xad, 0x9a, 0x5d, 0xd0, 0x5d, 0x89, 0x49, 0x6c, 0x52, 0xcc, 0x7f, + 0x6f, 0x0c, 0x06, 0x0b, 0x8b, 0xf7, 0xb5, 0xf7, 0x35, 0xf4, 0x05, 0x2c, 0x28, 0x31, 0x13, 0x89, + 0x2a, 0x36, 0x2e, 0x61, 0xe4, 0x37, 0xe3, 0x07, 0x79, 0x98, 0x3d, 0x20, 0xea, 0xfa, 0x91, 0x4e, + 0xaf, 0x7c, 0x5c, 0x47, 0x17, 0x6b, 0x1e, 0xce, 0x8b, 0x27, 0xee, 0x08, 0xc9, 0x3e, 0xac, 0xf0, + 0xa7, 0x0f, 0xa5, 0xf9, 0x3f, 0xa1, 0x37, 0x2c, 0xd1, 0xfa, 0x0f, 0x61, 0x85, 0xaf, 0xa5, 0xc2, + 0x26, 0x1b, 0x3c, 0xca, 0xf3, 0x36, 0xa2, 0x44, 0x06, 0x5f, 0xc0, 0x5a, 0x3d, 0xa2, 0x0f, 0x6b, + 0xd6, 0xba, 0xa5, 0xb2, 0x90, 0xba, 0xc2, 0x12, 0x79, 0x3d, 0x02, 0x54, 0xef, 0x9f, 0x5d, 0x5a, + 0x01, 0xbb, 0x2b, 0x0b, 0xbf, 0x40, 0x77, 0x22, 0x2a, 0x11, 0x20, 0x45, 0xa3, 0xe1, 0x33, 0x9f, + 0xa0, 0x31, 0x6a, 0xb0, 0xa7, 0x37, 0xd6, 0xd3, 0x61, 0x3a, 0xe6, 0x99, 0xd5, 0xb5, 0x7c, 0x0b, + 0x13, 0xb7, 0x90, 0x09, 0xe4, 0x21, 0xb1, 0x82, 0xb7, 0x12, 0x31, 0xd0, 0xe7, 0xb0, 0x70, 0x88, + 0xfd, 0xb0, 0xf1, 0x0d, 0x6d, 0x8c, 0xb4, 0xca, 0xf1, 0x75, 0x13, 0x97, 0x5e, 0x6a, 0xb7, 0x5d, + 0x0d, 0xb2, 0x27, 0x4e, 0xdb, 0xf4, 0xb1, 0xc4, 0xe2, 0xce, 0x08, 0x0b, 0x8e, 0x62, 0xba, 0xe6, + 0xa5, 0x97, 0x68, 0xad, 0x1d, 0x98, 0x3a, 0xb6, 0x7a, 0x1d, 0x24, 0x6e, 0xc6, 0xa4, 0x96, 0xa5, + 0xfc, 0x8a, 0x02, 0xe3, 0xae, 0xe7, 0x43, 0xe1, 0x9a, 0x8e, 0x2f, 0xf4, 0x93, 0xa0, 0x54, 0xb9, + 0x49, 0x67, 0x58, 0x5e, 0xda, 0xf7, 0xf1, 0x88, 0xcd, 0x5d, 0xf4, 0x73, 0xba, 0x0e, 0xa3, 0x18, + 0xe8, 0x1e, 0x9f, 0x6b, 0x5c, 0xdb, 0x58, 0xfe, 0x76, 0xe2, 0x0c, 0xcd, 0x5d, 0x74, 0x2a, 0x9e, + 0xff, 0x62, 0xb8, 0xbf, 0xa3, 0x74, 0xa4, 0xbc, 0xe6, 0x04, 0x3b, 0x34, 0xce, 0xd3, 0x5f, 0xa0, + 0x5a, 0x0b, 0xa5, 0x95, 0xba, 0x72, 0xf2, 0xea, 0xaf, 0x60, 0xa1, 0x5d, 0x1a, 0xd9, 0x69, 0x2f, + 0x28, 0x5a, 0x57, 0x29, 0xbc, 0x78, 0x92, 0xf7, 0x35, 0xb4, 0x0b, 0xc0, 0xa4, 0xa4, 0x13, 0xa9, + 0xc3, 0x89, 0xab, 0xbf, 0x4b, 0xc2, 0x7f, 0xfb, 0x15, 0x89, 0x3e, 0x17, 0x29, 0x80, 0x12, 0xe5, + 0x94, 0xf3, 0x91, 0xac, 0x55, 0x12, 0x7d, 0x0d, 0xb2, 0xa5, 0x16, 0x0d, 0x68, 0x41, 0xa3, 0x10, + 0xda, 0x0a, 0x36, 0x8b, 0x3a, 0x20, 0x78, 0xad, 0x45, 0xfb, 0x8e, 0x8e, 0x30, 0x29, 0x78, 0xab, + 0xb0, 0x11, 0xa4, 0xa6, 0xc8, 0x50, 0x3c, 0x45, 0xa2, 0x50, 0xfb, 0xb0, 0x5a, 0x31, 0x7b, 0x2d, + 0xdc, 0x7d, 0x33, 0x36, 0x9f, 0xd0, 0x9d, 0x2d, 0x35, 0x51, 0xad, 0x47, 0xe9, 0xf9, 0xc6, 0x16, + 0xcd, 0xe7, 0x12, 0x6a, 0x09, 0x96, 0x98, 0x11, 0x43, 0xb3, 0x24, 0x51, 0x27, 0x4d, 0xff, 0x11, + 0x2c, 0xee, 0x93, 0xc8, 0xd7, 0x6f, 0x5b, 0xac, 0x44, 0x46, 0x6a, 0xed, 0x94, 0x48, 0x58, 0x85, + 0x65, 0x9e, 0x08, 0xc2, 0xee, 0xa2, 0x20, 0xf8, 0x8e, 0x36, 0x70, 0xe5, 0x57, 0x05, 0x5b, 0xb9, + 0x11, 0x49, 0xa4, 0x39, 0xa5, 0x99, 0x26, 0x48, 0x73, 0x71, 0x4d, 0x3e, 0x41, 0x9a, 0x8b, 0xef, + 0xbf, 0x29, 0xc3, 0x52, 0xa4, 0x8f, 0x06, 0xdd, 0x11, 0xc9, 0x36, 0xb6, 0xbf, 0x26, 0xa6, 0x60, + 0xa9, 0x0a, 0xab, 0x8e, 0xf2, 0x88, 0x6f, 0xaa, 0x49, 0xb4, 0xd1, 0x71, 0x90, 0xe5, 0xe4, 0x0e, + 0x19, 0xa4, 0x5e, 0x10, 0xc4, 0x75, 0xcf, 0x24, 0x72, 0xac, 0x93, 0xb3, 0x8c, 0xda, 0x5b, 0x82, + 0xb6, 0x02, 0x8b, 0xc4, 0x36, 0xd0, 0xe4, 0x0b, 0x89, 0xe3, 0xdc, 0x68, 0xd2, 0x02, 0xb0, 0x5f, + 0x33, 0x8b, 0x2e, 0x80, 0xdc, 0x89, 0x30, 0xb2, 0x00, 0x6a, 0x83, 0xc1, 0x21, 0x7d, 0x4c, 0x92, + 0x9a, 0x44, 0x50, 0x82, 0x2a, 0xf9, 0x3b, 0x71, 0x7c, 0xc2, 0x95, 0xac, 0x43, 0x36, 0xda, 0x63, + 0x11, 0x68, 0x9a, 0xd0, 0x2c, 0x12, 0x68, 0x9a, 0xd8, 0x9c, 0xf1, 0x05, 0x64, 0xa3, 0x0d, 0x16, + 0x01, 0xd3, 0x84, 0xce, 0x8b, 0xc4, 0xa5, 0x38, 0x80, 0x55, 0x75, 0x01, 0xaf, 0xd1, 0x37, 0xb9, + 0x92, 0x59, 0x50, 0xda, 0x2a, 0x90, 0x48, 0x0d, 0x91, 0x0e, 0x8e, 0x11, 0xeb, 0xc7, 0xb4, 0x77, + 0x30, 0xeb, 0x4b, 0x2d, 0x1a, 0x37, 0xb1, 0x7e, 0x5c, 0x47, 0x47, 0x60, 0x28, 0x49, 0x2e, 0x11, + 0x71, 0xa3, 0x03, 0xaf, 0x62, 0xa8, 0x9b, 0x88, 0x96, 0xc4, 0x67, 0x0f, 0xe6, 0xa4, 0xde, 0x0e, + 0x74, 0x4b, 0x31, 0x93, 0xe2, 0xf1, 0x79, 0x45, 0x39, 0xd5, 0xd9, 0x2b, 0x30, 0x2f, 0x77, 0x88, + 0x24, 0x4a, 0x71, 0x7b, 0x94, 0x87, 0x27, 0x55, 0xd3, 0x8b, 0x81, 0x15, 0x98, 0x34, 0x9b, 0x51, + 0xe3, 0x28, 0x02, 0x25, 0xab, 0x84, 0x64, 0xd3, 0x5c, 0x23, 0x52, 0x72, 0x26, 0x5a, 0x61, 0x39, + 0x59, 0xfd, 0x05, 0xd7, 0x84, 0xdf, 0x93, 0x1d, 0x13, 0xad, 0x96, 0x22, 0x2d, 0x2d, 0x48, 0xf2, + 0x92, 0x98, 0x36, 0x90, 0xfc, 0x56, 0xd2, 0x30, 0x37, 0xd3, 0x11, 0x2c, 0x8f, 0x74, 0xb4, 0xa0, + 0x82, 0x12, 0x8f, 0x47, 0x5b, 0x53, 0xc6, 0x9c, 0xff, 0x96, 0x47, 0xda, 0x59, 0x02, 0x6e, 0x49, + 0x8d, 0x2e, 0x89, 0xdc, 0x1a, 0xb0, 0x16, 0xdb, 0xe2, 0x12, 0xd4, 0x87, 0xe3, 0x1a, 0x60, 0x12, + 0xb9, 0xfe, 0x1c, 0xd0, 0x68, 0x43, 0x4a, 0x70, 0x22, 0x4c, 0x6c, 0x99, 0x09, 0x4e, 0x84, 0x63, + 0xba, 0x59, 0x8e, 0x60, 0x35, 0xae, 0x1b, 0x05, 0xe9, 0x8a, 0x3d, 0x63, 0xbb, 0x49, 0x62, 0x92, + 0x9c, 0x21, 0x36, 0x65, 0x02, 0xb7, 0x31, 0xbd, 0x29, 0x89, 0xca, 0xff, 0x42, 0xf4, 0x1b, 0x8d, + 0xf6, 0x90, 0x04, 0x75, 0xf1, 0x35, 0x4d, 0x26, 0x63, 0xca, 0x8d, 0xa5, 0xba, 0xd5, 0xe9, 0x49, + 0xed, 0x1e, 0x41, 0xb1, 0x31, 0xda, 0x67, 0x12, 0x04, 0x80, 0xb8, 0xee, 0x90, 0xa7, 0xb0, 0x2a, + 0x32, 0xa1, 0xdc, 0x54, 0x81, 0x46, 0x68, 0xc2, 0x9e, 0x8f, 0x20, 0x18, 0xc4, 0x76, 0x61, 0xb0, + 0x6a, 0x9d, 0xfe, 0xd5, 0x02, 0xa9, 0x5a, 0x97, 0xba, 0x1d, 0xf2, 0x6a, 0x63, 0x04, 0x7a, 0x40, + 0xab, 0x75, 0xd6, 0x92, 0x9a, 0xb4, 0xd7, 0x37, 0x54, 0x4e, 0xa1, 0x1b, 0xec, 0x8a, 0xfb, 0x17, + 0x3a, 0xa1, 0xca, 0xf9, 0xfa, 0x02, 0x9c, 0x12, 0xa9, 0x05, 0xb8, 0x2c, 0x68, 0xf2, 0x09, 0x79, + 0x5e, 0x7e, 0x2a, 0x0a, 0x6c, 0x15, 0xf3, 0x18, 0x15, 0xd8, 0x2a, 0xee, 0x6d, 0x89, 0xd6, 0x7b, + 0x0d, 0x51, 0x5f, 0x85, 0xfc, 0xee, 0x8c, 0x7d, 0x1c, 0xca, 0x6f, 0x8d, 0x7f, 0x51, 0x91, 0xaa, + 0xc8, 0xf0, 0x2d, 0x43, 0x2e, 0x62, 0x46, 0x5e, 0x3e, 0xe4, 0x34, 0x1a, 0xf3, 0xfc, 0xf1, 0x98, + 0xde, 0x18, 0x3f, 0xad, 0xed, 0x55, 0xf8, 0x5f, 0x4f, 0xb0, 0xdd, 0x91, 0x8b, 0x2b, 0xe9, 0x77, + 0xe8, 0x42, 0xeb, 0x31, 0x14, 0x85, 0xb0, 0x59, 0x44, 0x75, 0x7a, 0xf7, 0xa2, 0x40, 0x63, 0xee, + 0xae, 0x62, 0x18, 0xe6, 0xe3, 0x19, 0xd2, 0x7b, 0x38, 0x1a, 0xf4, 0x89, 0x1f, 0xa8, 0x62, 0x26, + 0xc8, 0x30, 0x2e, 0x77, 0x30, 0xab, 0xc6, 0xb3, 0x11, 0xd2, 0x5d, 0xe7, 0x20, 0xcc, 0x62, 0xf5, + 0xd2, 0xe3, 0xa3, 0xd7, 0xb2, 0x98, 0x42, 0x18, 0x58, 0x4c, 0x81, 0xbe, 0x9a, 0xc5, 0x22, 0x0c, + 0x55, 0x8b, 0xa9, 0x62, 0x26, 0xc8, 0x70, 0xbd, 0xc5, 0xe2, 0xd9, 0xdc, 0xd4, 0x62, 0x5f, 0xd2, + 0x4c, 0x71, 0x48, 0x7f, 0x7f, 0xef, 0x95, 0x6c, 0x96, 0x13, 0x75, 0x8f, 0x4a, 0xda, 0xdc, 0x45, + 0xcf, 0x68, 0x1f, 0x6d, 0x04, 0x7e, 0x33, 0xbb, 0x6d, 0x26, 0x31, 0xa5, 0x96, 0xab, 0xc1, 0x1a, + 0xb3, 0x5c, 0x54, 0xdc, 0x44, 0x59, 0x12, 0xd5, 0x3e, 0x14, 0x69, 0x37, 0xca, 0xea, 0x55, 0xed, + 0xb7, 0x47, 0x5d, 0xa4, 0xe1, 0x92, 0xaa, 0xa6, 0x3d, 0x5a, 0xf2, 0xa8, 0x4c, 0xc4, 0x6d, 0x99, + 0x8a, 0xde, 0x2c, 0xa2, 0x1a, 0x5d, 0x05, 0x15, 0x3c, 0xae, 0x26, 0x8c, 0x67, 0x43, 0x8d, 0x54, + 0x15, 0xf9, 0x39, 0x22, 0x53, 0xd2, 0xdc, 0xc9, 0x42, 0x05, 0x05, 0xf3, 0x0d, 0xb5, 0x4b, 0x32, + 0x11, 0xcb, 0x33, 0xac, 0x3e, 0xbd, 0xce, 0x32, 0xd1, 0xbf, 0x6b, 0x84, 0xfe, 0x3f, 0x64, 0x04, + 0xf1, 0xf5, 0x06, 0x89, 0x52, 0x53, 0x83, 0x7c, 0x0e, 0x73, 0xdc, 0x20, 0x54, 0x82, 0xa4, 0x99, + 0x12, 0xc5, 0xff, 0x0c, 0xe6, 0xb8, 0x19, 0xc6, 0x6a, 0x90, 0x44, 0xde, 0xa4, 0xbd, 0xc1, 0x09, + 0x7f, 0x77, 0x24, 0x51, 0xa3, 0x6b, 0xff, 0xea, 0x09, 0xe1, 0x5b, 0x4f, 0xe6, 0x7b, 0x2d, 0xfd, + 0x98, 0x9b, 0xb5, 0x4c, 0xf0, 0x4c, 0x89, 0xa4, 0xf4, 0xaf, 0x3c, 0xd4, 0xe5, 0x17, 0xe4, 0x27, + 0x21, 0x0f, 0x95, 0x58, 0xdc, 0x95, 0x9f, 0xf4, 0xa4, 0xfb, 0x80, 0xd8, 0xb7, 0xbe, 0x08, 0x8b, + 0x72, 0xf6, 0xdb, 0xff, 0xdc, 0xd2, 0xbe, 0xfd, 0x6e, 0x4b, 0xfb, 0xd7, 0xef, 0xb6, 0xb4, 0x3f, + 0x7e, 0xb7, 0xa5, 0x9d, 0xcd, 0xd0, 0xf1, 0xdd, 0xff, 0x0e, 0x00, 0x00, 0xff, 0xff, 0x9e, 0x55, + 0x2d, 0xec, 0x8e, 0x4d, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -15326,11 +15326,18 @@ func (m *Events) MarshalToSizedBuffer(dAtA []byte) (int, error) { dAtA[i] = 0x12 } if len(m.Items) > 0 { - i -= len(m.Items) - copy(dAtA[i:], m.Items) - i = encodeVarintAuthservice(dAtA, i, uint64(len(m.Items))) - i-- - dAtA[i] = 0xa + for iNdEx := len(m.Items) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Items[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintAuthservice(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } } return len(dAtA) - i, nil } @@ -17161,7 +17168,7 @@ func (m *AddMFADeviceRequest_Init) Size() (n int) { return n } func (m *AddMFADeviceRequest_ExistingMFAResponse) Size() (n int) { - if m == nil { + if m == nil { return 0 } var l int @@ -17669,9 +17676,11 @@ func (m *Events) Size() (n int) { } var l int _ = l - l = len(m.Items) - if l > 0 { - n += 1 + l + sovAuthservice(uint64(l)) + if len(m.Items) > 0 { + for _, e := range m.Items { + l = e.Size() + n += 1 + l + sovAuthservice(uint64(l)) + } } l = len(m.LastKey) if l > 0 { @@ -20228,7 +20237,7 @@ func (m *RequestStateSetter) Unmarshal(dAtA []byte) error { } m.Roles = append(m.Roles, string(dAtA[iNdEx:postIndex])) iNdEx = postIndex - default: + default: iNdEx = preIndex skippy, err := skipAuthservice(dAtA[iNdEx:]) if err != nil { @@ -28757,7 +28766,7 @@ func (m *Events) Unmarshal(dAtA []byte) error { if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Items", wireType) } - var byteLen int + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowAuthservice @@ -28767,24 +28776,24 @@ func (m *Events) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - byteLen |= int(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - if byteLen < 0 { + if msglen < 0 { return ErrInvalidLengthAuthservice } - postIndex := iNdEx + byteLen + postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLengthAuthservice } if postIndex > l { return io.ErrUnexpectedEOF } - m.Items = append(m.Items[:0], dAtA[iNdEx:postIndex]...) - if m.Items == nil { - m.Items = []byte{} + m.Items = append(m.Items, &events.OneOf{}) + if err := m.Items[len(m.Items)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err } iNdEx = postIndex case 2: diff --git a/api/client/proto/authservice.proto b/api/client/proto/authservice.proto index 0cf9ee7e111d3..142b0a66b6b63 100644 --- a/api/client/proto/authservice.proto +++ b/api/client/proto/authservice.proto @@ -833,7 +833,7 @@ message GetSessionEventsRequest { } message Events { - bytes Items = 1; + repeated events.OneOf Items = 1; // the key of the last event if the returned set did not contain all events found i.e limit < // actual amount. this is the key clients can supply in another API request to continue fetching // events from the previous last position diff --git a/e b/e index 7d5351ff4f954..dee3585af7968 160000 --- a/e +++ b/e @@ -1 +1 @@ -Subproject commit 7d5351ff4f954fe8180141e8e3e9b3db9b2c5b5b +Subproject commit dee3585af79684e1931efc32c6e1ecc7cae92dfa diff --git a/lib/auth/auth_with_roles.go b/lib/auth/auth_with_roles.go index 246437a6421a0..868926d1e65c8 100644 --- a/lib/auth/auth_with_roles.go +++ b/lib/auth/auth_with_roles.go @@ -2899,7 +2899,7 @@ func (a *ServerWithRoles) IsMFARequired(ctx context.Context, req *proto.IsMFAReq } // SearchEvents allows searching audit events with pagination support. -func (a *ServerWithRoles) SearchEvents(fromUTC, toUTC time.Time, namespace string, eventTypes []string, limit int, startKey string) (events []events.EventFields, lastKey string, err error) { +func (a *ServerWithRoles) SearchEvents(fromUTC, toUTC time.Time, namespace string, eventTypes []string, limit int, startKey string) (events []events.AuditEvent, lastKey string, err error) { if err := a.action(defaults.Namespace, services.KindEvent, services.VerbList); err != nil { return nil, "", trace.Wrap(err) } @@ -2913,7 +2913,7 @@ func (a *ServerWithRoles) SearchEvents(fromUTC, toUTC time.Time, namespace strin } // SearchSessionEvents allows searching session audit events with pagination support. -func (a *ServerWithRoles) SearchSessionEvents(fromUTC, toUTC time.Time, limit int, startKey string) (events []events.EventFields, lastKey string, err error) { +func (a *ServerWithRoles) SearchSessionEvents(fromUTC, toUTC time.Time, limit int, startKey string) (events []events.AuditEvent, lastKey string, err error) { if err := a.action(defaults.Namespace, services.KindEvent, services.VerbList); err != nil { return nil, "", trace.Wrap(err) } diff --git a/lib/auth/clt.go b/lib/auth/clt.go index 23de4f0fc4a90..17281faea9689 100644 --- a/lib/auth/clt.go +++ b/lib/auth/clt.go @@ -1452,35 +1452,23 @@ func (c *Client) GetSessionEvents(namespace string, sid session.ID, afterN int, } // SearchEvents allows searching for audit events with pagination support. -func (c *Client) SearchEvents(fromUTC, toUTC time.Time, namespace string, eventTypes []string, limit int, startKey string) ([]events.EventFields, string, error) { - eventsOpaque, lastKey, err := c.APIClient.SearchEvents(context.TODO(), fromUTC, toUTC, namespace, eventTypes, limit, startKey) +func (c *Client) SearchEvents(fromUTC, toUTC time.Time, namespace string, eventTypes []string, limit int, startKey string) ([]events.AuditEvent, string, error) { + events, lastKey, err := c.APIClient.SearchEvents(context.TODO(), fromUTC, toUTC, namespace, eventTypes, limit, startKey) if err != nil { return nil, "", trace.Wrap(err) } - var eventsConcrete []events.EventFields - - for _, event := range eventsOpaque { - eventsConcrete = append(eventsConcrete, events.EventFields(event)) - } - - return eventsConcrete, lastKey, nil + return events, lastKey, nil } // SearchSessionEvents returns session related events to find completed sessions. -func (c *Client) SearchSessionEvents(fromUTC time.Time, toUTC time.Time, limit int, startKey string) ([]events.EventFields, string, error) { - eventsOpaque, lastKey, err := c.APIClient.SearchSessionEvents(context.TODO(), fromUTC, toUTC, limit, startKey) +func (c *Client) SearchSessionEvents(fromUTC time.Time, toUTC time.Time, limit int, startKey string) ([]events.AuditEvent, string, error) { + events, lastKey, err := c.APIClient.SearchSessionEvents(context.TODO(), fromUTC, toUTC, limit, startKey) if err != nil { return nil, "", trace.Wrap(err) } - var eventsConcrete []events.EventFields - - for _, event := range eventsOpaque { - eventsConcrete = append(eventsConcrete, events.EventFields(event)) - } - - return eventsConcrete, lastKey, nil + return events, lastKey, nil } // GetNamespaces returns a list of namespaces diff --git a/lib/auth/grpcserver.go b/lib/auth/grpcserver.go index e49a62080c500..9a13b0a52d1f6 100644 --- a/lib/auth/grpcserver.go +++ b/lib/auth/grpcserver.go @@ -19,7 +19,6 @@ package auth import ( "context" "crypto/tls" - "encoding/json" "io" "net" "time" @@ -2520,16 +2519,21 @@ func (g *GRPCServer) GetEvents(ctx context.Context, req *proto.GetEventsRequest) return nil, trail.ToGRPC(err) } - events, lastkey, err := auth.ServerWithRoles.SearchEvents(req.StartDate, req.EndDate, req.Namespace, req.EventTypes, int(req.Limit), req.StartKey) + rawEvents, lastkey, err := auth.ServerWithRoles.SearchEvents(req.StartDate, req.EndDate, req.Namespace, req.EventTypes, int(req.Limit), req.StartKey) if err != nil { return nil, trail.ToGRPC(err) } var res *proto.Events = &proto.Events{} - encodedEvents, err := json.Marshal(events) - if err != nil { - return nil, trail.ToGRPC(err) + encodedEvents := make([]*apievents.OneOf, len(rawEvents)) + + for _, rawEvent := range rawEvents { + event, err := events.ToOneOf(rawEvent) + if err != nil { + return nil, trail.ToGRPC(err) + } + encodedEvents = append(encodedEvents, event) } res.Items = encodedEvents @@ -2544,16 +2548,21 @@ func (g *GRPCServer) GetSessionEvents(ctx context.Context, req *proto.GetSession return nil, trail.ToGRPC(err) } - events, lastkey, err := auth.ServerWithRoles.SearchSessionEvents(req.StartDate, req.EndDate, int(req.Limit), req.StartKey) + rawEvents, lastkey, err := auth.ServerWithRoles.SearchSessionEvents(req.StartDate, req.EndDate, int(req.Limit), req.StartKey) if err != nil { return nil, trail.ToGRPC(err) } var res *proto.Events = &proto.Events{} - encodedEvents, err := json.Marshal(events) - if err != nil { - return nil, trail.ToGRPC(err) + encodedEvents := make([]*apievents.OneOf, len(rawEvents)) + + for _, rawEvent := range rawEvents { + event, err := events.ToOneOf(rawEvent) + if err != nil { + return nil, trail.ToGRPC(err) + } + encodedEvents = append(encodedEvents, event) } res.Items = encodedEvents diff --git a/lib/events/api.go b/lib/events/api.go index d8f970f1403cb..257480c5771c1 100644 --- a/lib/events/api.go +++ b/lib/events/api.go @@ -589,7 +589,7 @@ type IAuditLog interface { // // The only mandatory requirement is a date range (UTC). Results must always // show up sorted by date (newest first) - SearchEvents(fromUTC, toUTC time.Time, namespace string, eventTypes []string, limit int, startKey string) ([]EventFields, string, error) + SearchEvents(fromUTC, toUTC time.Time, namespace string, eventTypes []string, limit int, startKey string) ([]AuditEvent, string, error) // SearchSessionEvents is a flexible way to find session events. // Only session events are returned by this function. @@ -597,7 +597,7 @@ type IAuditLog interface { // // Event types to filter can be specified and pagination is handled by an iterator key that allows // a query to be resumed. - SearchSessionEvents(fromUTC time.Time, toUTC time.Time, limit int, startKey string) ([]EventFields, string, error) + SearchSessionEvents(fromUTC time.Time, toUTC time.Time, limit int, startKey string) ([]AuditEvent, string, error) // WaitForDelivery waits for resources to be released and outstanding requests to // complete after calling Close method diff --git a/lib/events/auditlog.go b/lib/events/auditlog.go index fe8e9a9ef4a6c..b36148fb73c61 100644 --- a/lib/events/auditlog.go +++ b/lib/events/auditlog.go @@ -1016,7 +1016,7 @@ func (l *AuditLog) auditDirs() ([]string, error) { return out, nil } -func (l *AuditLog) SearchEvents(fromUTC, toUTC time.Time, namespace string, eventType []string, limit int, startKey string) ([]EventFields, string, error) { +func (l *AuditLog) SearchEvents(fromUTC, toUTC time.Time, namespace string, eventType []string, limit int, startKey string) ([]AuditEvent, string, error) { l.log.Debugf("SearchEvents(%v, %v, namespace=%v, eventType=%v, limit=%v)", fromUTC, toUTC, namespace, eventType, limit) if limit <= 0 { limit = defaults.EventsIterationLimit @@ -1030,7 +1030,7 @@ func (l *AuditLog) SearchEvents(fromUTC, toUTC time.Time, namespace string, even return l.localLog.SearchEvents(fromUTC, toUTC, namespace, eventType, limit, startKey) } -func (l *AuditLog) SearchSessionEvents(fromUTC, toUTC time.Time, limit int, startKey string) ([]EventFields, string, error) { +func (l *AuditLog) SearchSessionEvents(fromUTC, toUTC time.Time, limit int, startKey string) ([]AuditEvent, string, error) { l.log.Debugf("SearchSessionEvents(%v, %v, %v)", fromUTC, toUTC, limit) if l.ExternalLog != nil { @@ -1219,11 +1219,11 @@ func (a *closedLogger) GetSessionEvents(namespace string, sid session.ID, after return nil, trace.NotImplemented(loggerClosedMessage) } -func (a *closedLogger) SearchEvents(fromUTC, toUTC time.Time, namespace string, eventType []string, limit int, startKey string) ([]EventFields, string, error) { +func (a *closedLogger) SearchEvents(fromUTC, toUTC time.Time, namespace string, eventType []string, limit int, startKey string) ([]AuditEvent, string, error) { return nil, "", trace.NotImplemented(loggerClosedMessage) } -func (a *closedLogger) SearchSessionEvents(fromUTC time.Time, toUTC time.Time, limit int, startKey string) ([]EventFields, string, error) { +func (a *closedLogger) SearchSessionEvents(fromUTC time.Time, toUTC time.Time, limit int, startKey string) ([]AuditEvent, string, error) { return nil, "", trace.NotImplemented(loggerClosedMessage) } diff --git a/lib/events/discard.go b/lib/events/discard.go index 15bd3c3c78fd6..ab6450b6c7366 100644 --- a/lib/events/discard.go +++ b/lib/events/discard.go @@ -51,11 +51,11 @@ func (d *DiscardAuditLog) GetSessionChunk(namespace string, sid session.ID, offs func (d *DiscardAuditLog) GetSessionEvents(namespace string, sid session.ID, after int, includePrintEvents bool) ([]EventFields, error) { return make([]EventFields, 0), nil } -func (d *DiscardAuditLog) SearchEvents(fromUTC, toUTC time.Time, namespace string, eventType []string, limit int, startKey string) ([]EventFields, string, error) { - return make([]EventFields, 0), "", nil +func (d *DiscardAuditLog) SearchEvents(fromUTC, toUTC time.Time, namespace string, eventType []string, limit int, startKey string) ([]AuditEvent, string, error) { + return make([]AuditEvent, 0), "", nil } -func (d *DiscardAuditLog) SearchSessionEvents(fromUTC time.Time, toUTC time.Time, limit int, startKey string) ([]EventFields, string, error) { - return make([]EventFields, 0), "", nil +func (d *DiscardAuditLog) SearchSessionEvents(fromUTC time.Time, toUTC time.Time, limit int, startKey string) ([]AuditEvent, string, error) { + return make([]AuditEvent, 0), "", nil } func (d *DiscardAuditLog) UploadSessionRecording(SessionRecording) error { diff --git a/lib/events/dynamic.go b/lib/events/dynamic.go new file mode 100644 index 0000000000000..8f0082e18ea89 --- /dev/null +++ b/lib/events/dynamic.go @@ -0,0 +1,29 @@ +/* +Copyright 2021 Gravitational, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package events + +func FromEventFields(fields EventFields) (AuditEvent, error) { + panic("unimplemented") +} + +func GetSessionID(event AuditEvent) string { + panic("unimplemented") +} + +func ToEventFields(event AuditEvent) (EventFields, error) { + panic("unimplemented") +} diff --git a/lib/events/dynamoevents/dynamoevents.go b/lib/events/dynamoevents/dynamoevents.go index 3981ada65b7ed..daec613c85a9f 100644 --- a/lib/events/dynamoevents/dynamoevents.go +++ b/lib/events/dynamoevents/dynamoevents.go @@ -658,7 +658,7 @@ type checkpointKey struct { // // The only mandatory requirement is a date range (UTC). Results must always // show up sorted by date (newest first) -func (l *Log) SearchEvents(fromUTC, toUTC time.Time, namespace string, eventTypes []string, limit int, startKey string) ([]events.EventFields, string, error) { +func (l *Log) SearchEvents(fromUTC, toUTC time.Time, namespace string, eventTypes []string, limit int, startKey string) ([]events.AuditEvent, string, error) { var checkpoint checkpointKey // If a checkpoint key is provided, unmarshal it so we can work with it's parts. @@ -776,7 +776,16 @@ dateLoop: } } - return values, string(lastKey), nil + eventArr := make([]events.AuditEvent, len(values)) + for _, fields := range values { + event, err := events.FromEventFields(fields) + if err != nil { + return nil, "", trace.Wrap(err) + } + eventArr = append(eventArr, event) + } + + return eventArr, string(lastKey), nil } // Used in tests to check full backend data. @@ -900,7 +909,7 @@ dateLoop: // SearchSessionEvents returns session related events only. This is used to // find completed session. -func (l *Log) SearchSessionEvents(fromUTC time.Time, toUTC time.Time, limit int, startKey string) ([]events.EventFields, string, error) { +func (l *Log) SearchSessionEvents(fromUTC time.Time, toUTC time.Time, limit int, startKey string) ([]events.AuditEvent, string, error) { // only search for specific event types query := []string{ events.SessionStartEvent, diff --git a/lib/events/filelog.go b/lib/events/filelog.go index 5385962573c7f..6ed4683f07285 100644 --- a/lib/events/filelog.go +++ b/lib/events/filelog.go @@ -194,7 +194,7 @@ func (l *FileLog) EmitAuditEventLegacy(event Event, fields EventFields) error { return nil } -func (l *FileLog) SearchEvents(fromUTC, toUTC time.Time, namespace string, eventTypes []string, limit int, startKey string) ([]EventFields, string, error) { +func (l *FileLog) SearchEvents(fromUTC, toUTC time.Time, namespace string, eventTypes []string, limit int, startKey string) ([]AuditEvent, string, error) { l.Debugf("SearchEvents(%v, %v, namespace=%v, eventType=%v, limit=%v)", fromUTC, toUTC, namespace, eventTypes, limit) if limit <= 0 { limit = defaults.EventsIterationLimit @@ -215,14 +215,14 @@ func (l *FileLog) SearchEvents(fromUTC, toUTC time.Time, namespace string, event var total int var lastKey string // search within each file: - events := make([]EventFields, 0) + dynamicEvents := make([]EventFields, 0) for i := range filtered { var found []EventFields found, lastKey, foundStart, err = l.findInFile(filtered[i].path, eventTypes, &total, limit, startKey, foundStart) if err != nil { return nil, "", trace.Wrap(err) } - events = append(events, found...) + dynamicEvents = append(dynamicEvents, found...) if limit > 0 && total >= limit { break } @@ -231,11 +231,21 @@ func (l *FileLog) SearchEvents(fromUTC, toUTC time.Time, namespace string, event // in case if events are associated with the same session, to make // sure that events are not displayed out of order in case of multiple // auth servers. - sort.Sort(ByTimeAndIndex(events)) + sort.Sort(ByTimeAndIndex(dynamicEvents)) + + events := make([]AuditEvent, 0) + for _, dynamicEvent := range dynamicEvents { + event, err := FromEventFields(dynamicEvent) + if err != nil { + return nil, "", trace.Wrap(err) + } + events = append(events, event) + } + return events, lastKey, nil } -func (l *FileLog) SearchSessionEvents(fromUTC, toUTC time.Time, limit int, startKey string) ([]EventFields, string, error) { +func (l *FileLog) SearchSessionEvents(fromUTC, toUTC time.Time, limit int, startKey string) ([]AuditEvent, string, error) { l.Debugf("SearchSessionEvents(%v, %v, %v)", fromUTC, toUTC, limit) // only search for specific event types @@ -253,11 +263,11 @@ func (l *FileLog) SearchSessionEvents(fromUTC, toUTC time.Time, limit int, start // filter out 'session end' events that do not // have a corresponding 'session start' event started := make(map[string]struct{}, len(events)/2) - filtered := make([]EventFields, 0, len(events)) + filtered := make([]AuditEvent, 0, len(events)) for i := range events { event := events[i] - eventType := event[EventType] - sessionID := event.GetString(SessionEventID) + eventType := event.GetType() + sessionID := GetSessionID(event) if sessionID == "" { continue } diff --git a/lib/events/firestoreevents/firestoreevents.go b/lib/events/firestoreevents/firestoreevents.go index a1b8d66a68dc5..da0ecd4c62f3d 100644 --- a/lib/events/firestoreevents/firestoreevents.go +++ b/lib/events/firestoreevents/firestoreevents.go @@ -459,7 +459,7 @@ func (l *Log) GetSessionEvents(namespace string, sid session.ID, after int, inlc return values, nil } -func (l *Log) SearchEvents(fromUTC, toUTC time.Time, namespace string, eventTypes []string, limit int, startKey string) ([]events.EventFields, string, error) { +func (l *Log) SearchEvents(fromUTC, toUTC time.Time, namespace string, eventTypes []string, limit int, startKey string) ([]events.AuditEvent, string, error) { g := l.WithFields(log.Fields{"From": fromUTC, "To": toUTC, "Namespace": namespace, "EventTypes": eventTypes, "Limit": limit, "StartKey": startKey}) doFilter := len(eventTypes) > 0 @@ -526,12 +526,22 @@ func (l *Log) SearchEvents(fromUTC, toUTC time.Time, namespace string, eventType } } sort.Sort(events.ByTimeAndIndex(values)) - return values, fmt.Sprintf("%d", lastKey), nil + + eventArr := make([]events.AuditEvent, len(values)) + for _, fields := range values { + event, err := events.FromEventFields(fields) + if err != nil { + return nil, "", trace.Wrap(err) + } + eventArr = append(eventArr, event) + } + + return eventArr, fmt.Sprintf("%d", lastKey), nil } // SearchSessionEvents returns session related events only. This is used to // find completed session. -func (l *Log) SearchSessionEvents(fromUTC time.Time, toUTC time.Time, limit int, startKey string) ([]events.EventFields, string, error) { +func (l *Log) SearchSessionEvents(fromUTC time.Time, toUTC time.Time, limit int, startKey string) ([]events.AuditEvent, string, error) { // only search for specific event types query := []string{ events.SessionStartEvent, diff --git a/lib/events/forward.go b/lib/events/forward.go index acb87b0373f66..2c60b71f07e43 100644 --- a/lib/events/forward.go +++ b/lib/events/forward.go @@ -242,13 +242,13 @@ func (l *Forwarder) GetSessionEvents(namespace string, sid session.ID, after int // // The only mandatory requirement is a date range (UTC). Results must always // show up sorted by date (newest first) -func (l *Forwarder) SearchEvents(fromUTC, toUTC time.Time, namespace string, eventType []string, limit int, startKey string) ([]EventFields, string, error) { +func (l *Forwarder) SearchEvents(fromUTC, toUTC time.Time, namespace string, eventType []string, limit int, startKey string) ([]AuditEvent, string, error) { return l.ForwardTo.SearchEvents(fromUTC, toUTC, namespace, eventType, limit, startKey) } // SearchSessionEvents returns session related events only. This is used to // find completed session. -func (l *Forwarder) SearchSessionEvents(fromUTC time.Time, toUTC time.Time, limit int, startKey string) ([]EventFields, string, error) { +func (l *Forwarder) SearchSessionEvents(fromUTC time.Time, toUTC time.Time, limit int, startKey string) ([]AuditEvent, string, error) { return l.ForwardTo.SearchSessionEvents(fromUTC, toUTC, limit, startKey) } diff --git a/lib/events/multilog.go b/lib/events/multilog.go index e681ce03a77d6..d84cdd4c8b2f7 100644 --- a/lib/events/multilog.go +++ b/lib/events/multilog.go @@ -133,7 +133,7 @@ func (m *MultiLog) GetSessionEvents(namespace string, sid session.ID, after int, // // The only mandatory requirement is a date range (UTC). Results must always // show up sorted by date (newest first) -func (m *MultiLog) SearchEvents(fromUTC, toUTC time.Time, namespace string, eventTypes []string, limit int, startKey string) (events []EventFields, lastKey string, err error) { +func (m *MultiLog) SearchEvents(fromUTC, toUTC time.Time, namespace string, eventTypes []string, limit int, startKey string) (events []AuditEvent, lastKey string, err error) { for _, log := range m.loggers { events, lastKey, err := log.SearchEvents(fromUTC, toUTC, namespace, eventTypes, limit, startKey) if !trace.IsNotImplemented(err) { @@ -149,7 +149,7 @@ func (m *MultiLog) SearchEvents(fromUTC, toUTC time.Time, namespace string, even // // Event types to filter can be specified and pagination is handled by an iterator key that allows // a query to be resumed. -func (m *MultiLog) SearchSessionEvents(fromUTC, toUTC time.Time, limit int, startKey string) (events []EventFields, lastKey string, err error) { +func (m *MultiLog) SearchSessionEvents(fromUTC, toUTC time.Time, limit int, startKey string) (events []AuditEvent, lastKey string, err error) { for _, log := range m.loggers { events, lastKey, err = log.SearchSessionEvents(fromUTC, toUTC, limit, startKey) if !trace.IsNotImplemented(err) { diff --git a/lib/events/writer.go b/lib/events/writer.go index 12eae970412ac..8b931b24a12a2 100644 --- a/lib/events/writer.go +++ b/lib/events/writer.go @@ -110,7 +110,7 @@ func (w *WriterLog) GetSessionEvents(namespace string, sid session.ID, after int // // The only mandatory requirement is a date range (UTC). Results must always // show up sorted by date (newest first) -func (w *WriterLog) SearchEvents(fromUTC, toUTC time.Time, namespace string, eventTypes []string, limit int, startKey string) (events []EventFields, lastKey string, err error) { +func (w *WriterLog) SearchEvents(fromUTC, toUTC time.Time, namespace string, eventTypes []string, limit int, startKey string) (events []AuditEvent, lastKey string, err error) { return nil, "", trace.NotImplemented("not implemented") } @@ -120,7 +120,7 @@ func (w *WriterLog) SearchEvents(fromUTC, toUTC time.Time, namespace string, eve // // Event types to filter can be specified and pagination is handled by an iterator key that allows // a query to be resumed. -func (w *WriterLog) SearchSessionEvents(fromUTC, toUTC time.Time, limit int, startKey string) (events []EventFields, lastKey string, err error) { +func (w *WriterLog) SearchSessionEvents(fromUTC, toUTC time.Time, limit int, startKey string) (events []AuditEvent, lastKey string, err error) { return nil, "", trace.NotImplemented("not implemented") } diff --git a/lib/web/apiserver.go b/lib/web/apiserver.go index 951b67b2b4f5e..a875614ea1058 100644 --- a/lib/web/apiserver.go +++ b/lib/web/apiserver.go @@ -1864,6 +1864,19 @@ func (h *Handler) siteSessionGet(w http.ResponseWriter, r *http.Request, p httpr const maxStreamBytes = 5 * 1024 * 1024 +func toArrayFields(rawEvents []events.AuditEvent) ([]events.EventFields, error) { + el := make([]events.EventFields, 0, len(rawEvents)) + for _, event := range rawEvents { + els, err := events.ToEventFields(event) + if err != nil { + return nil, trace.Wrap(err) + } + el = append(el, els) + } + + return el, nil +} + // clusterSearchSessionEvents allows to search for session events on a cluster // // GET /v1/webapi/sites/:site/events @@ -1904,10 +1917,16 @@ func (h *Handler) clusterSearchSessionEvents(w http.ResponseWriter, r *http.Requ } } - el, _, err := clt.SearchSessionEvents(from, to, defaults.EventsIterationLimit, "") + rawEvents, _, err := clt.SearchSessionEvents(from, to, defaults.EventsIterationLimit, "") + if err != nil { + return nil, trace.Wrap(err) + } + + el, err := toArrayFields(rawEvents) if err != nil { return nil, trace.Wrap(err) } + return eventsListGetResponse{Events: el}, nil } @@ -1946,11 +1965,17 @@ func (h *Handler) clusterSearchEvents(w http.ResponseWriter, r *http.Request, p eventTypes = strings.Split(include, ";") } - fields, _, err := clt.SearchEvents(from, to, defaults.Namespace, eventTypes, limit, "") + rawEvents, _, err := clt.SearchEvents(from, to, defaults.Namespace, eventTypes, limit, "") if err != nil { return nil, trace.Wrap(err) } - return eventsListGetResponse{Events: fields}, nil + + el, err := toArrayFields(rawEvents) + if err != nil { + return nil, trace.Wrap(err) + } + + return eventsListGetResponse{Events: el}, nil } // queryTime parses the query string parameter with the specified name as a From 112fc0963977f780043a468cdb3d54145508bd81 Mon Sep 17 00:00:00 2001 From: xacrimon Date: Thu, 13 May 2021 01:22:00 +0200 Subject: [PATCH 03/28] migrated some tests --- lib/auth/tls_test.go | 10 ++++++---- lib/events/auditlog_test.go | 8 ++++++-- lib/events/dynamoevents/dynamoevents_test.go | 4 ++++ lib/events/test/suite.go | 13 +++++++------ 4 files changed, 23 insertions(+), 12 deletions(-) diff --git a/lib/auth/tls_test.go b/lib/auth/tls_test.go index 4303fb1dfd687..c36e3bcda4eb1 100644 --- a/lib/auth/tls_test.go +++ b/lib/auth/tls_test.go @@ -1299,13 +1299,13 @@ func (s *TLSSuite) TestSharedSessions(c *check.C) { Time: time.Now().UTC().UnixNano(), EventIndex: 0, EventType: events.SessionStartEvent, - Data: marshal(events.EventFields{events.EventLogin: "alice", "val": "three"}), + Data: marshal(events.EventFields{events.EventLogin: "alice"}), }, { Time: time.Now().UTC().UnixNano(), EventIndex: 1, EventType: events.SessionEndEvent, - Data: marshal(events.EventFields{events.EventLogin: "alice", "val": "three"}), + Data: marshal(events.EventFields{events.EventLogin: "alice"}), }, }, Version: events.V3, @@ -1360,9 +1360,11 @@ func (s *TLSSuite) TestSharedSessions(c *check.C) { c.Assert(len(history), check.Equals, 2) var found bool for _, event := range history { - if event.GetString(events.SessionEventID) == string(anotherSessionID) { + realEvent, ok := event.(*events.SessionEnd) + c.Assert(ok, check.Equals, true) + if realEvent.GetSessionID() == string(anotherSessionID) { found = true - c.Assert(event.GetString("val"), check.Equals, "three") + c.Assert(realEvent.Login, check.Equals, "alice") } } c.Assert(found, check.Equals, true) diff --git a/lib/events/auditlog_test.go b/lib/events/auditlog_test.go index a92981a4672ce..a09b44069e817 100644 --- a/lib/events/auditlog_test.go +++ b/lib/events/auditlog_test.go @@ -299,8 +299,12 @@ func (a *AuditTestSuite) TestSessionRecordingOff(c *check.C) { found, _, err := alog.SearchEvents(now.Add(-time.Hour), now.Add(time.Hour), defaults.Namespace, nil, 0, "") c.Assert(err, check.IsNil) c.Assert(found, check.HasLen, 3) - c.Assert(found[0].GetString(EventLogin), check.Equals, username) - c.Assert(found[1].GetString(EventLogin), check.Equals, username) + eventA, okA := found[0].(*SessionStart) + eventB, okB := found[0].(*SessionEnd) + c.Assert(okA, check.Equals, true) + c.Assert(okB, check.Equals, true) + c.Assert(eventA.Login, check.Equals, username) + c.Assert(eventB.Login, check.Equals, username) // inspect the session log for "200", should have two events history, err := alog.GetSessionEvents(defaults.Namespace, session.ID(sessionID), 0, true) diff --git a/lib/events/dynamoevents/dynamoevents_test.go b/lib/events/dynamoevents/dynamoevents_test.go index 6bd540b082a0f..a6d2a789afeb9 100644 --- a/lib/events/dynamoevents/dynamoevents_test.go +++ b/lib/events/dynamoevents/dynamoevents_test.go @@ -84,6 +84,10 @@ func (s *DynamoeventsSuite) SetUpTest(c *check.C) { c.Assert(err, check.IsNil) } +func (s *DynamoeventsSuite) TestPagination(c *check.C) { + s.TestPagination(c) +} + func (s *DynamoeventsSuite) TestSessionEventsCRUD(c *check.C) { s.SessionEventsCRUD(c) diff --git a/lib/events/test/suite.go b/lib/events/test/suite.go index 199c686ced2a1..c3af2a1acc6ba 100644 --- a/lib/events/test/suite.go +++ b/lib/events/test/suite.go @@ -98,7 +98,7 @@ func (s *EventsSuite) EventPagination(c *check.C) { } toTime := baseTime.Add(time.Hour) - var arr []events.EventFields + var arr []events.AuditEvent var err error var checkpoint string @@ -106,8 +106,9 @@ func (s *EventsSuite) EventPagination(c *check.C) { arr, checkpoint, err = s.Log.SearchEvents(baseTime, toTime, defaults.Namespace, nil, 1, checkpoint) c.Assert(err, check.IsNil) c.Assert(arr, check.HasLen, 1) - eventName := arr[0].GetString(events.EventUser) - c.Assert(name, check.Equals, eventName) + event, ok := arr[0].(*events.UserLogin) + c.Assert(ok, check.Equals, true) + c.Assert(name, check.Equals, event.User) } } @@ -157,11 +158,11 @@ func (s *EventsSuite) SessionEventsCRUD(c *check.C) { c.Assert(err, check.IsNil) // read the session event - history, err = s.Log.GetSessionEvents(defaults.Namespace, sessionID, 0, false) + historyEvents, err := s.Log.GetSessionEvents(defaults.Namespace, sessionID, 0, false) c.Assert(err, check.IsNil) c.Assert(history, check.HasLen, 2) - c.Assert(history[0].GetString(events.EventType), check.Equals, events.SessionStartEvent) - c.Assert(history[1].GetString(events.EventType), check.Equals, events.SessionEndEvent) + c.Assert(historyEvents[0].GetString(events.EventType), check.Equals, events.SessionStartEvent) + c.Assert(historyEvents[1].GetString(events.EventType), check.Equals, events.SessionEndEvent) history, _, err = s.Log.SearchSessionEvents(s.Clock.Now().Add(-1*time.Hour), s.Clock.Now().Add(2*time.Hour), 100, "") c.Assert(err, check.IsNil) From 730765187bb0837351bb430eddf9b90592c94207 Mon Sep 17 00:00:00 2001 From: xacrimon Date: Thu, 13 May 2021 01:28:01 +0200 Subject: [PATCH 04/28] fix optimized array sizing --- api/client/client.go | 4 ++-- lib/auth/grpcserver.go | 4 ++-- lib/events/dynamoevents/dynamoevents.go | 2 +- lib/events/filelog.go | 2 +- lib/events/firestoreevents/firestoreevents.go | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/api/client/client.go b/api/client/client.go index 7b5959015c57b..6e506434cfb0b 100644 --- a/api/client/client.go +++ b/api/client/client.go @@ -1309,7 +1309,7 @@ func (c *Client) SearchEvents(ctx context.Context, fromUTC, toUTC time.Time, nam return nil, "", trail.FromGRPC(err) } - decodedEvents := make([]events.AuditEvent, len(response.Items)) + decodedEvents := make([]events.AuditEvent, 0, len(response.Items)) for _, rawEvent := range response.Items { event, err := events.FromOneOf(*rawEvent) if err != nil { @@ -1335,7 +1335,7 @@ func (c *Client) SearchSessionEvents(ctx context.Context, fromUTC time.Time, toU return nil, "", trail.FromGRPC(err) } - decodedEvents := make([]events.AuditEvent, len(response.Items)) + decodedEvents := make([]events.AuditEvent, 0, len(response.Items)) for _, rawEvent := range response.Items { event, err := events.FromOneOf(*rawEvent) if err != nil { diff --git a/lib/auth/grpcserver.go b/lib/auth/grpcserver.go index 9a13b0a52d1f6..2758998471d14 100644 --- a/lib/auth/grpcserver.go +++ b/lib/auth/grpcserver.go @@ -2526,7 +2526,7 @@ func (g *GRPCServer) GetEvents(ctx context.Context, req *proto.GetEventsRequest) var res *proto.Events = &proto.Events{} - encodedEvents := make([]*apievents.OneOf, len(rawEvents)) + encodedEvents := make([]*apievents.OneOf, 0, len(rawEvents)) for _, rawEvent := range rawEvents { event, err := events.ToOneOf(rawEvent) @@ -2555,7 +2555,7 @@ func (g *GRPCServer) GetSessionEvents(ctx context.Context, req *proto.GetSession var res *proto.Events = &proto.Events{} - encodedEvents := make([]*apievents.OneOf, len(rawEvents)) + encodedEvents := make([]*apievents.OneOf, 0, len(rawEvents)) for _, rawEvent := range rawEvents { event, err := events.ToOneOf(rawEvent) diff --git a/lib/events/dynamoevents/dynamoevents.go b/lib/events/dynamoevents/dynamoevents.go index daec613c85a9f..afd5c82c7ea38 100644 --- a/lib/events/dynamoevents/dynamoevents.go +++ b/lib/events/dynamoevents/dynamoevents.go @@ -776,7 +776,7 @@ dateLoop: } } - eventArr := make([]events.AuditEvent, len(values)) + eventArr := make([]events.AuditEvent, 0, len(values)) for _, fields := range values { event, err := events.FromEventFields(fields) if err != nil { diff --git a/lib/events/filelog.go b/lib/events/filelog.go index 6ed4683f07285..762b3a20dbe9a 100644 --- a/lib/events/filelog.go +++ b/lib/events/filelog.go @@ -233,7 +233,7 @@ func (l *FileLog) SearchEvents(fromUTC, toUTC time.Time, namespace string, event // auth servers. sort.Sort(ByTimeAndIndex(dynamicEvents)) - events := make([]AuditEvent, 0) + events := make([]AuditEvent, 0, len(dynamicEvents)) for _, dynamicEvent := range dynamicEvents { event, err := FromEventFields(dynamicEvent) if err != nil { diff --git a/lib/events/firestoreevents/firestoreevents.go b/lib/events/firestoreevents/firestoreevents.go index da0ecd4c62f3d..93450ed45e4b1 100644 --- a/lib/events/firestoreevents/firestoreevents.go +++ b/lib/events/firestoreevents/firestoreevents.go @@ -527,7 +527,7 @@ func (l *Log) SearchEvents(fromUTC, toUTC time.Time, namespace string, eventType } sort.Sort(events.ByTimeAndIndex(values)) - eventArr := make([]events.AuditEvent, len(values)) + eventArr := make([]events.AuditEvent, 0, len(values)) for _, fields := range values { event, err := events.FromEventFields(fields) if err != nil { From 9a368274cf390e88dc7d33706c3cac124e71115b Mon Sep 17 00:00:00 2001 From: xacrimon Date: Thu, 13 May 2021 21:32:14 +0200 Subject: [PATCH 05/28] implemented ToEventFields and GetSessionID --- lib/events/dynamic.go | 38 +++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/lib/events/dynamic.go b/lib/events/dynamic.go index 8f0082e18ea89..948d6dc9728e4 100644 --- a/lib/events/dynamic.go +++ b/lib/events/dynamic.go @@ -16,14 +16,46 @@ limitations under the License. package events +import ( + "github.com/gravitational/teleport/lib/utils" + "github.com/gravitational/trace" + + "encoding/json" +) + +// FromEventFields converts from the typed dynamic representation +// to the new typed interface-style representation. +// +// This is mainly used to convert from the backend format used by +// our various event backends. func FromEventFields(fields EventFields) (AuditEvent, error) { panic("unimplemented") } -func GetSessionID(event AuditEvent) string { - panic("unimplemented") +// GetSessionID pulls the session ID from the events that have a +// SessionMetadata. For other events an empty string is returned. +func GetSessionID(event AuditEvent) (string, error) { + fields, err := ToEventFields(event) + if err != nil { + return "", trace.Wrap(err) + } + + return fields.GetString(SessionEventID), nil } +// ToEventFields converts from the typed interface-style event representation +// to the old dynamic map style representation in order to provide outer compatability +// with existing public API routes when the backend is updated with the typed events. func ToEventFields(event AuditEvent) (EventFields, error) { - panic("unimplemented") + encoded, err := utils.FastMarshal(event) + if err != nil { + return nil, trace.Wrap(err) + } + + var fields EventFields + if err := json.Unmarshal(encoded, &fields); err != nil { + return nil, trace.BadParameter("failed to unmarshal event %v", err) + } + + return fields, nil } From 3ad572821e31cfccd047514c4cc61cde6bb8299c Mon Sep 17 00:00:00 2001 From: xacrimon Date: Thu, 13 May 2021 21:41:39 +0200 Subject: [PATCH 06/28] update some code to handle an error --- lib/events/filelog.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/events/filelog.go b/lib/events/filelog.go index 762b3a20dbe9a..06883055a417a 100644 --- a/lib/events/filelog.go +++ b/lib/events/filelog.go @@ -267,7 +267,10 @@ func (l *FileLog) SearchSessionEvents(fromUTC, toUTC time.Time, limit int, start for i := range events { event := events[i] eventType := event.GetType() - sessionID := GetSessionID(event) + sessionID, err := GetSessionID(event) + if err != nil { + return nil, "", trace.Wrap(err) + } if sessionID == "" { continue } From 1c99f18d956d3295388e4bf0e871fde64a66d92d Mon Sep 17 00:00:00 2001 From: xacrimon Date: Thu, 13 May 2021 23:44:30 +0200 Subject: [PATCH 07/28] add deserialization code --- lib/events/dynamic.go | 313 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 312 insertions(+), 1 deletion(-) diff --git a/lib/events/dynamic.go b/lib/events/dynamic.go index 948d6dc9728e4..9bb663484854b 100644 --- a/lib/events/dynamic.go +++ b/lib/events/dynamic.go @@ -17,6 +17,7 @@ limitations under the License. package events import ( + "github.com/gravitational/teleport/api/types/events" "github.com/gravitational/teleport/lib/utils" "github.com/gravitational/trace" @@ -29,7 +30,317 @@ import ( // This is mainly used to convert from the backend format used by // our various event backends. func FromEventFields(fields EventFields) (AuditEvent, error) { - panic("unimplemented") + data, err := json.Marshal(fields) + if err != nil { + return nil, trace.Wrap(err) + } + + eventType := fields.GetString(EventType) + + switch eventType { + case SessionPrintEvent: + var e events.SessionPrint + if err := json.Unmarshal(data, &e); err != nil { + return nil, trace.Wrap(err) + } + return &e, nil + case SessionStartEvent: + var e events.SessionStart + if err := json.Unmarshal(data, &e); err != nil { + return nil, trace.Wrap(err) + } + return &e, nil + case SessionEndEvent: + var e events.SessionEnd + if err := json.Unmarshal(data, &e); err != nil { + return nil, trace.Wrap(err) + } + return &e, nil + case SessionUploadEvent: + var e events.SessionUpload + if err := json.Unmarshal(data, &e); err != nil { + return nil, trace.Wrap(err) + } + return &e, nil + case SessionJoinEvent: + var e events.SessionJoin + if err := json.Unmarshal(data, &e); err != nil { + return nil, trace.Wrap(err) + } + return &e, nil + case SessionLeaveEvent: + var e events.SessionLeave + if err := json.Unmarshal(data, &e); err != nil { + return nil, trace.Wrap(err) + } + return &e, nil + case SessionDataEvent: + var e events.SessionData + if err := json.Unmarshal(data, &e); err != nil { + return nil, trace.Wrap(err) + } + return &e, nil + case ClientDisconnectEvent: + var e events.ClientDisconnect + if err := json.Unmarshal(data, &e); err != nil { + return nil, trace.Wrap(err) + } + return &e, nil + case UserLoginEvent: + var e events.UserLogin + if err := json.Unmarshal(data, &e); err != nil { + return nil, trace.Wrap(err) + } + return &e, nil + case UserDeleteEvent: + var e events.UserDelete + if err := json.Unmarshal(data, &e); err != nil { + return nil, trace.Wrap(err) + } + return &e, nil + case UserCreateEvent: + var e events.UserCreate + if err := json.Unmarshal(data, &e); err != nil { + return nil, trace.Wrap(err) + } + return &e, nil + case UserPasswordChangeEvent: + var e events.UserPasswordChange + if err := json.Unmarshal(data, &e); err != nil { + return nil, trace.Wrap(err) + } + return &e, nil + case AccessRequestCreateEvent: + var e events.AccessRequestCreate + if err := json.Unmarshal(data, &e); err != nil { + return nil, trace.Wrap(err) + } + return &e, nil + case AccessRequestUpdateEvent: + var e events.AccessRequestCreate + if err := json.Unmarshal(data, &e); err != nil { + return nil, trace.Wrap(err) + } + return &e, nil + case BillingCardCreateEvent: + var e events.BillingCardCreate + if err := json.Unmarshal(data, &e); err != nil { + return nil, trace.Wrap(err) + } + return &e, nil + case BillingCardUpdateEvent: + var e events.BillingCardCreate + if err := json.Unmarshal(data, &e); err != nil { + return nil, trace.Wrap(err) + } + return &e, nil + case BillingCardDeleteEvent: + var e events.BillingCardDelete + if err := json.Unmarshal(data, &e); err != nil { + return nil, trace.Wrap(err) + } + return &e, nil + case BillingInformationUpdateEvent: + var e events.BillingInformationUpdate + if err := json.Unmarshal(data, &e); err != nil { + return nil, trace.Wrap(err) + } + return &e, nil + case ResetPasswordTokenCreateEvent: + var e events.ResetPasswordTokenCreate + if err := json.Unmarshal(data, &e); err != nil { + return nil, trace.Wrap(err) + } + return &e, nil + case ExecEvent: + var e events.Exec + if err := json.Unmarshal(data, &e); err != nil { + return nil, trace.Wrap(err) + } + return &e, nil + case SubsystemEvent: + var e events.Subsystem + if err := json.Unmarshal(data, &e); err != nil { + return nil, trace.Wrap(err) + } + return &e, nil + case X11ForwardEvent: + var e events.X11Forward + if err := json.Unmarshal(data, &e); err != nil { + return nil, trace.Wrap(err) + } + return &e, nil + case PortForwardEvent: + var e events.PortForward + if err := json.Unmarshal(data, &e); err != nil { + return nil, trace.Wrap(err) + } + return &e, nil + case AuthAttemptEvent: + var e events.AuthAttempt + if err := json.Unmarshal(data, &e); err != nil { + return nil, trace.Wrap(err) + } + return &e, nil + case SCPEvent: + var e events.SCP + if err := json.Unmarshal(data, &e); err != nil { + return nil, trace.Wrap(err) + } + return &e, nil + case ResizeEvent: + var e events.Resize + if err := json.Unmarshal(data, &e); err != nil { + return nil, trace.Wrap(err) + } + return &e, nil + case SessionCommandEvent: + var e events.SessionCommand + if err := json.Unmarshal(data, &e); err != nil { + return nil, trace.Wrap(err) + } + return &e, nil + case SessionDiskEvent: + var e events.SessionDisk + if err := json.Unmarshal(data, &e); err != nil { + return nil, trace.Wrap(err) + } + return &e, nil + case SessionNetworkEvent: + var e events.SessionNetwork + if err := json.Unmarshal(data, &e); err != nil { + return nil, trace.Wrap(err) + } + return &e, nil + case RoleCreatedEvent: + var e events.RoleCreate + if err := json.Unmarshal(data, &e); err != nil { + return nil, trace.Wrap(err) + } + return &e, nil + case RoleDeletedEvent: + var e events.RoleDelete + if err := json.Unmarshal(data, &e); err != nil { + return nil, trace.Wrap(err) + } + return &e, nil + case TrustedClusterCreateEvent: + var e events.TrustedClusterCreate + if err := json.Unmarshal(data, &e); err != nil { + return nil, trace.Wrap(err) + } + return &e, nil + case TrustedClusterDeleteEvent: + var e events.TrustedClusterDelete + if err := json.Unmarshal(data, &e); err != nil { + return nil, trace.Wrap(err) + } + return &e, nil + case TrustedClusterTokenCreateEvent: + var e events.TrustedClusterTokenCreate + if err := json.Unmarshal(data, &e); err != nil { + return nil, trace.Wrap(err) + } + return &e, nil + case GithubConnectorCreatedEvent: + var e events.GithubConnectorCreate + if err := json.Unmarshal(data, &e); err != nil { + return nil, trace.Wrap(err) + } + return &e, nil + case GithubConnectorDeletedEvent: + var e events.GithubConnectorDelete + if err := json.Unmarshal(data, &e); err != nil { + return nil, trace.Wrap(err) + } + return &e, nil + case OIDCConnectorCreatedEvent: + var e events.OIDCConnectorCreate + if err := json.Unmarshal(data, &e); err != nil { + return nil, trace.Wrap(err) + } + return &e, nil + case OIDCConnectorDeletedEvent: + var e events.OIDCConnectorDelete + if err := json.Unmarshal(data, &e); err != nil { + return nil, trace.Wrap(err) + } + return &e, nil + case SAMLConnectorCreatedEvent: + var e events.SAMLConnectorCreate + if err := json.Unmarshal(data, &e); err != nil { + return nil, trace.Wrap(err) + } + return &e, nil + case SAMLConnectorDeletedEvent: + var e events.SAMLConnectorDelete + if err := json.Unmarshal(data, &e); err != nil { + return nil, trace.Wrap(err) + } + return &e, nil + case SessionRejectedEvent: + var e events.SessionReject + if err := json.Unmarshal(data, &e); err != nil { + return nil, trace.Wrap(err) + } + return &e, nil + case AppSessionStartEvent: + var e events.AppSessionStart + if err := json.Unmarshal(data, &e); err != nil { + return nil, trace.Wrap(err) + } + return &e, nil + case AppSessionChunkEvent: + var e events.AppSessionChunk + if err := json.Unmarshal(data, &e); err != nil { + return nil, trace.Wrap(err) + } + return &e, nil + case AppSessionRequestEvent: + var e events.AppSessionRequest + if err := json.Unmarshal(data, &e); err != nil { + return nil, trace.Wrap(err) + } + return &e, nil + case DatabaseSessionStartEvent: + var e events.DatabaseSessionStart + if err := json.Unmarshal(data, &e); err != nil { + return nil, trace.Wrap(err) + } + return &e, nil + case DatabaseSessionEndEvent: + var e events.DatabaseSessionEnd + if err := json.Unmarshal(data, &e); err != nil { + return nil, trace.Wrap(err) + } + return &e, nil + case DatabaseSessionQueryEvent: + var e events.DatabaseSessionQuery + if err := json.Unmarshal(data, &e); err != nil { + return nil, trace.Wrap(err) + } + return &e, nil + case KubeRequestEvent: + var e events.KubeRequest + if err := json.Unmarshal(data, &e); err != nil { + return nil, trace.Wrap(err) + } + return &e, nil + case MFADeviceAddEvent: + var e events.MFADeviceAdd + if err := json.Unmarshal(data, &e); err != nil { + return nil, trace.Wrap(err) + } + return &e, nil + case MFADeviceDeleteEvent: + var e events.MFADeviceDelete + if err := json.Unmarshal(data, &e); err != nil { + return nil, trace.Wrap(err) + } + return &e, nil + default: + return nil, trace.BadParameter("unknown event type: %v", eventType) + } } // GetSessionID pulls the session ID from the events that have a From f13f52b3e521469342a276f91e718ef268507fc8 Mon Sep 17 00:00:00 2001 From: xacrimon Date: Thu, 13 May 2021 23:50:30 +0200 Subject: [PATCH 08/28] update test mock impl --- lib/srv/exec_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/srv/exec_test.go b/lib/srv/exec_test.go index 079875ede9a75..0ee5f2a4d7949 100644 --- a/lib/srv/exec_test.go +++ b/lib/srv/exec_test.go @@ -539,11 +539,11 @@ func (a *fakeLog) GetSessionEvents(namespace string, sid rsession.ID, after int, return nil, trace.NotFound("") } -func (a *fakeLog) SearchEvents(fromUTC, toUTC time.Time, namespace string, eventTypes []string, limit int, startKey string) ([]events.EventFields, string, error) { +func (a *fakeLog) SearchEvents(fromUTC, toUTC time.Time, namespace string, eventTypes []string, limit int, startKey string) ([]events.AuditEvent, string, error) { return nil, "", trace.NotFound("") } -func (a *fakeLog) SearchSessionEvents(fromUTC time.Time, toUTC time.Time, limit int, startKey string) ([]events.EventFields, string, error) { +func (a *fakeLog) SearchSessionEvents(fromUTC time.Time, toUTC time.Time, limit int, startKey string) ([]events.AuditEvent, string, error) { return nil, "", trace.NotFound("") } From 1caf18e0bf3e2a320f217eac087e8336147de72c Mon Sep 17 00:00:00 2001 From: xacrimon Date: Thu, 13 May 2021 23:56:24 +0200 Subject: [PATCH 09/28] fix lint --- lib/events/dynamic.go | 2 +- lib/events/dynamoevents/dynamoevents_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/events/dynamic.go b/lib/events/dynamic.go index 9bb663484854b..47c473809f325 100644 --- a/lib/events/dynamic.go +++ b/lib/events/dynamic.go @@ -355,7 +355,7 @@ func GetSessionID(event AuditEvent) (string, error) { } // ToEventFields converts from the typed interface-style event representation -// to the old dynamic map style representation in order to provide outer compatability +// to the old dynamic map style representation in order to provide outer compatibility // with existing public API routes when the backend is updated with the typed events. func ToEventFields(event AuditEvent) (EventFields, error) { encoded, err := utils.FastMarshal(event) diff --git a/lib/events/dynamoevents/dynamoevents_test.go b/lib/events/dynamoevents/dynamoevents_test.go index a6d2a789afeb9..cda56b7f56551 100644 --- a/lib/events/dynamoevents/dynamoevents_test.go +++ b/lib/events/dynamoevents/dynamoevents_test.go @@ -85,7 +85,7 @@ func (s *DynamoeventsSuite) SetUpTest(c *check.C) { } func (s *DynamoeventsSuite) TestPagination(c *check.C) { - s.TestPagination(c) + s.EventPagination(c) } func (s *DynamoeventsSuite) TestSessionEventsCRUD(c *check.C) { From df8ce880f6a82dc297b36745ac87ddda007e1f05 Mon Sep 17 00:00:00 2001 From: xacrimon Date: Fri, 14 May 2021 12:59:51 +0200 Subject: [PATCH 10/28] apply feedback --- lib/events/dynamic.go | 13 +++++++------ lib/events/filelog.go | 5 +---- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/lib/events/dynamic.go b/lib/events/dynamic.go index 47c473809f325..cdce2545b86a2 100644 --- a/lib/events/dynamic.go +++ b/lib/events/dynamic.go @@ -339,19 +339,20 @@ func FromEventFields(fields EventFields) (AuditEvent, error) { } return &e, nil default: - return nil, trace.BadParameter("unknown event type: %v", eventType) + return nil, trace.BadParameter("unknown event type: %q", eventType) } } // GetSessionID pulls the session ID from the events that have a // SessionMetadata. For other events an empty string is returned. -func GetSessionID(event AuditEvent) (string, error) { - fields, err := ToEventFields(event) - if err != nil { - return "", trace.Wrap(err) +func GetSessionID(event AuditEvent) string { + var sessionID string + + if g, ok := event.(SessionMetadataGetter); ok { + sessionID = g.GetSessionID() } - return fields.GetString(SessionEventID), nil + return sessionID } // ToEventFields converts from the typed interface-style event representation diff --git a/lib/events/filelog.go b/lib/events/filelog.go index 06883055a417a..762b3a20dbe9a 100644 --- a/lib/events/filelog.go +++ b/lib/events/filelog.go @@ -267,10 +267,7 @@ func (l *FileLog) SearchSessionEvents(fromUTC, toUTC time.Time, limit int, start for i := range events { event := events[i] eventType := event.GetType() - sessionID, err := GetSessionID(event) - if err != nil { - return nil, "", trace.Wrap(err) - } + sessionID := GetSessionID(event) if sessionID == "" { continue } From 954422dbe36d63d572bb9244d6eae085f5caca6d Mon Sep 17 00:00:00 2001 From: xacrimon Date: Fri, 14 May 2021 13:08:55 +0200 Subject: [PATCH 11/28] refactor auditlog test --- lib/events/auditlog_test.go | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/events/auditlog_test.go b/lib/events/auditlog_test.go index a09b44069e817..dd76e69f0661c 100644 --- a/lib/events/auditlog_test.go +++ b/lib/events/auditlog_test.go @@ -31,6 +31,7 @@ import ( "github.com/jonboulle/clockwork" "github.com/gravitational/teleport" + "github.com/gravitational/teleport/api/types/events" "github.com/gravitational/teleport/lib/defaults" "github.com/gravitational/teleport/lib/fixtures" "github.com/gravitational/teleport/lib/session" @@ -355,7 +356,10 @@ func (a *AuditTestSuite) TestLogRotation(c *check.C) { clock.Advance(duration) // emit regular event: - err = alog.EmitAuditEventLegacy(Event{Name: "user.joined"}, EventFields{"apples?": "yes"}) + event := &events.Resize{ + TerminalSize: "10:10", + } + err = alog.EmitAuditEvent(context.TODO(), event) c.Assert(err, check.IsNil) logfile := alog.localLog.file.Name() @@ -367,13 +371,14 @@ func (a *AuditTestSuite) TestLogRotation(c *check.C) { // read back what's been written: bytes, err := ioutil.ReadFile(logfile) c.Assert(err, check.IsNil) - contents := fmt.Sprintf("{\"apples?\":\"yes\",\"event\":\"user.joined\",\"time\":\"%s\",\"uid\":\"%s\"}\n", now.Format(time.RFC3339), fixtures.UUID) - c.Assert(string(bytes), check.Equals, contents) + contents, err := json.Marshal(event) + c.Assert(err, check.IsNil) + c.Assert(string(bytes), check.Equals, string(contents)) // read back the contents using symlink bytes, err = ioutil.ReadFile(filepath.Join(alog.localLog.SymlinkDir, SymlinkFilename)) c.Assert(err, check.IsNil) - c.Assert(string(bytes), check.Equals, contents) + c.Assert(string(bytes), check.Equals, string(contents)) found, _, err := alog.SearchEvents(now.Add(-time.Hour), now.Add(time.Hour), defaults.Namespace, nil, 0, "") c.Assert(err, check.IsNil) From 44ee806ca46aed7d00733537da64f3d0b79f8aad Mon Sep 17 00:00:00 2001 From: xacrimon Date: Fri, 14 May 2021 13:23:18 +0200 Subject: [PATCH 12/28] newline check --- lib/events/auditlog_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/events/auditlog_test.go b/lib/events/auditlog_test.go index dd76e69f0661c..2a9b39789aa87 100644 --- a/lib/events/auditlog_test.go +++ b/lib/events/auditlog_test.go @@ -372,6 +372,7 @@ func (a *AuditTestSuite) TestLogRotation(c *check.C) { bytes, err := ioutil.ReadFile(logfile) c.Assert(err, check.IsNil) contents, err := json.Marshal(event) + contents = append(contents, '\n') c.Assert(err, check.IsNil) c.Assert(string(bytes), check.Equals, string(contents)) From 5afea94b3de4cc2aa6e45fb3565d955c5b29347a Mon Sep 17 00:00:00 2001 From: xacrimon Date: Fri, 14 May 2021 13:24:40 +0200 Subject: [PATCH 13/28] fix test --- lib/events/auditlog_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/events/auditlog_test.go b/lib/events/auditlog_test.go index 2a9b39789aa87..0b9343d6f93d6 100644 --- a/lib/events/auditlog_test.go +++ b/lib/events/auditlog_test.go @@ -301,7 +301,7 @@ func (a *AuditTestSuite) TestSessionRecordingOff(c *check.C) { c.Assert(err, check.IsNil) c.Assert(found, check.HasLen, 3) eventA, okA := found[0].(*SessionStart) - eventB, okB := found[0].(*SessionEnd) + eventB, okB := found[1].(*SessionEnd) c.Assert(okA, check.Equals, true) c.Assert(okB, check.Equals, true) c.Assert(eventA.Login, check.Equals, username) From e9148284ab11ed1f774a268d8e58c93f6dfbcb1e Mon Sep 17 00:00:00 2001 From: xacrimon Date: Fri, 14 May 2021 13:41:45 +0200 Subject: [PATCH 14/28] more helpful event conversion error --- lib/events/dynamic.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/events/dynamic.go b/lib/events/dynamic.go index cdce2545b86a2..c136df74d4ab4 100644 --- a/lib/events/dynamic.go +++ b/lib/events/dynamic.go @@ -339,7 +339,7 @@ func FromEventFields(fields EventFields) (AuditEvent, error) { } return &e, nil default: - return nil, trace.BadParameter("unknown event type: %q", eventType) + return nil, trace.BadParameter("unknown event type: %q", fields) } } From 467e0ee9acd6e424fe5c98ab78c7b49ccfac28e2 Mon Sep 17 00:00:00 2001 From: xacrimon Date: Fri, 14 May 2021 18:23:23 +0200 Subject: [PATCH 15/28] adjust comments --- api/client/proto/authservice.proto | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/api/client/proto/authservice.proto b/api/client/proto/authservice.proto index abfcdc0a9c0a2..dd4560bbb89cb 100644 --- a/api/client/proto/authservice.proto +++ b/api/client/proto/authservice.proto @@ -833,13 +833,17 @@ message GetEventsRequest { } message GetSessionEventsRequest { - // Oldest date of returned events + // StartDate is the oldest date of returned events google.protobuf.Timestamp StartDate = 1 [ (gogoproto.stdtime) = true, (gogoproto.nullable) = false ]; - // Newest date of returned events + // EndDate is the newest date of returned events google.protobuf.Timestamp EndDate = 2 [ (gogoproto.stdtime) = true, (gogoproto.nullable) = false ]; + // Limit is the maximum amount of events to retrieve. int32 Limit = 3; + // StartKey is used to resume a query in order to enable pagination. + // If the previous response had LastKey set then this should be + // set to its value. Otherwise leave empty. string StartKey = 4; } From 58011e566f13b8d0984c4065673ef339af0169b0 Mon Sep 17 00:00:00 2001 From: xacrimon Date: Fri, 14 May 2021 18:24:27 +0200 Subject: [PATCH 16/28] adjust session query priveleges --- lib/auth/auth_with_roles.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/auth/auth_with_roles.go b/lib/auth/auth_with_roles.go index 868926d1e65c8..65d2b2dafcece 100644 --- a/lib/auth/auth_with_roles.go +++ b/lib/auth/auth_with_roles.go @@ -2914,7 +2914,7 @@ func (a *ServerWithRoles) SearchEvents(fromUTC, toUTC time.Time, namespace strin // SearchSessionEvents allows searching session audit events with pagination support. func (a *ServerWithRoles) SearchSessionEvents(fromUTC, toUTC time.Time, limit int, startKey string) (events []events.AuditEvent, lastKey string, err error) { - if err := a.action(defaults.Namespace, services.KindEvent, services.VerbList); err != nil { + if err := a.action(defaults.Namespace, services.KindSession, services.VerbList); err != nil { return nil, "", trace.Wrap(err) } From 742d9b2d5e377f4ca2436aac62a3412e0d21fbbc Mon Sep 17 00:00:00 2001 From: xacrimon Date: Fri, 14 May 2021 18:31:29 +0200 Subject: [PATCH 17/28] adjust log --- lib/events/auditlog.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/events/auditlog.go b/lib/events/auditlog.go index b36148fb73c61..2156f23b2fbf6 100644 --- a/lib/events/auditlog.go +++ b/lib/events/auditlog.go @@ -1017,7 +1017,8 @@ func (l *AuditLog) auditDirs() ([]string, error) { } func (l *AuditLog) SearchEvents(fromUTC, toUTC time.Time, namespace string, eventType []string, limit int, startKey string) ([]AuditEvent, string, error) { - l.log.Debugf("SearchEvents(%v, %v, namespace=%v, eventType=%v, limit=%v)", fromUTC, toUTC, namespace, eventType, limit) + g := l.log.WithFields(log.Fields{"namespace": namespace, "eventType": eventType, "limit": limit}) + g.Debugf("SearchEvents(%v, %v)", fromUTC, toUTC) if limit <= 0 { limit = defaults.EventsIterationLimit } From fe95d6bb8326f58de001f3a3703f4bc703e1184d Mon Sep 17 00:00:00 2001 From: xacrimon Date: Fri, 14 May 2021 18:31:50 +0200 Subject: [PATCH 18/28] log event type only --- lib/events/dynamic.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/events/dynamic.go b/lib/events/dynamic.go index c136df74d4ab4..cdce2545b86a2 100644 --- a/lib/events/dynamic.go +++ b/lib/events/dynamic.go @@ -339,7 +339,7 @@ func FromEventFields(fields EventFields) (AuditEvent, error) { } return &e, nil default: - return nil, trace.BadParameter("unknown event type: %q", fields) + return nil, trace.BadParameter("unknown event type: %q", eventType) } } From 12d2c5e7e287927da4c6e3dcaaa51f8bb46d5277 Mon Sep 17 00:00:00 2001 From: xacrimon Date: Fri, 14 May 2021 18:33:32 +0200 Subject: [PATCH 19/28] use objecttostruct --- lib/events/dynamic.go | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/lib/events/dynamic.go b/lib/events/dynamic.go index cdce2545b86a2..4ee21d0306e16 100644 --- a/lib/events/dynamic.go +++ b/lib/events/dynamic.go @@ -359,14 +359,9 @@ func GetSessionID(event AuditEvent) string { // to the old dynamic map style representation in order to provide outer compatibility // with existing public API routes when the backend is updated with the typed events. func ToEventFields(event AuditEvent) (EventFields, error) { - encoded, err := utils.FastMarshal(event) - if err != nil { - return nil, trace.Wrap(err) - } - var fields EventFields - if err := json.Unmarshal(encoded, &fields); err != nil { - return nil, trace.BadParameter("failed to unmarshal event %v", err) + if err := utils.ObjectToStruct(event, fields); err != nil { + return nil, trace.Wrap(err) } return fields, nil From 2509fa4621f288c62e192d6aacbfff1052f7da40 Mon Sep 17 00:00:00 2001 From: xacrimon Date: Fri, 14 May 2021 18:34:28 +0200 Subject: [PATCH 20/28] use fast unmarshalling --- lib/events/dynamic.go | 100 +++++++++++++++++++++--------------------- 1 file changed, 50 insertions(+), 50 deletions(-) diff --git a/lib/events/dynamic.go b/lib/events/dynamic.go index 4ee21d0306e16..5492ba7e71576 100644 --- a/lib/events/dynamic.go +++ b/lib/events/dynamic.go @@ -40,301 +40,301 @@ func FromEventFields(fields EventFields) (AuditEvent, error) { switch eventType { case SessionPrintEvent: var e events.SessionPrint - if err := json.Unmarshal(data, &e); err != nil { + if err := utils.FastUnmarshal(data, &e); err != nil { return nil, trace.Wrap(err) } return &e, nil case SessionStartEvent: var e events.SessionStart - if err := json.Unmarshal(data, &e); err != nil { + if err := utils.FastUnmarshal(data, &e); err != nil { return nil, trace.Wrap(err) } return &e, nil case SessionEndEvent: var e events.SessionEnd - if err := json.Unmarshal(data, &e); err != nil { + if err := utils.FastUnmarshal(data, &e); err != nil { return nil, trace.Wrap(err) } return &e, nil case SessionUploadEvent: var e events.SessionUpload - if err := json.Unmarshal(data, &e); err != nil { + if err := utils.FastUnmarshal(data, &e); err != nil { return nil, trace.Wrap(err) } return &e, nil case SessionJoinEvent: var e events.SessionJoin - if err := json.Unmarshal(data, &e); err != nil { + if err := utils.FastUnmarshal(data, &e); err != nil { return nil, trace.Wrap(err) } return &e, nil case SessionLeaveEvent: var e events.SessionLeave - if err := json.Unmarshal(data, &e); err != nil { + if err := utils.FastUnmarshal(data, &e); err != nil { return nil, trace.Wrap(err) } return &e, nil case SessionDataEvent: var e events.SessionData - if err := json.Unmarshal(data, &e); err != nil { + if err := utils.FastUnmarshal(data, &e); err != nil { return nil, trace.Wrap(err) } return &e, nil case ClientDisconnectEvent: var e events.ClientDisconnect - if err := json.Unmarshal(data, &e); err != nil { + if err := utils.FastUnmarshal(data, &e); err != nil { return nil, trace.Wrap(err) } return &e, nil case UserLoginEvent: var e events.UserLogin - if err := json.Unmarshal(data, &e); err != nil { + if err := utils.FastUnmarshal(data, &e); err != nil { return nil, trace.Wrap(err) } return &e, nil case UserDeleteEvent: var e events.UserDelete - if err := json.Unmarshal(data, &e); err != nil { + if err := utils.FastUnmarshal(data, &e); err != nil { return nil, trace.Wrap(err) } return &e, nil case UserCreateEvent: var e events.UserCreate - if err := json.Unmarshal(data, &e); err != nil { + if err := utils.FastUnmarshal(data, &e); err != nil { return nil, trace.Wrap(err) } return &e, nil case UserPasswordChangeEvent: var e events.UserPasswordChange - if err := json.Unmarshal(data, &e); err != nil { + if err := utils.FastUnmarshal(data, &e); err != nil { return nil, trace.Wrap(err) } return &e, nil case AccessRequestCreateEvent: var e events.AccessRequestCreate - if err := json.Unmarshal(data, &e); err != nil { + if err := utils.FastUnmarshal(data, &e); err != nil { return nil, trace.Wrap(err) } return &e, nil case AccessRequestUpdateEvent: var e events.AccessRequestCreate - if err := json.Unmarshal(data, &e); err != nil { + if err := utils.FastUnmarshal(data, &e); err != nil { return nil, trace.Wrap(err) } return &e, nil case BillingCardCreateEvent: var e events.BillingCardCreate - if err := json.Unmarshal(data, &e); err != nil { + if err := utils.FastUnmarshal(data, &e); err != nil { return nil, trace.Wrap(err) } return &e, nil case BillingCardUpdateEvent: var e events.BillingCardCreate - if err := json.Unmarshal(data, &e); err != nil { + if err := utils.FastUnmarshal(data, &e); err != nil { return nil, trace.Wrap(err) } return &e, nil case BillingCardDeleteEvent: var e events.BillingCardDelete - if err := json.Unmarshal(data, &e); err != nil { + if err := utils.FastUnmarshal(data, &e); err != nil { return nil, trace.Wrap(err) } return &e, nil case BillingInformationUpdateEvent: var e events.BillingInformationUpdate - if err := json.Unmarshal(data, &e); err != nil { + if err := utils.FastUnmarshal(data, &e); err != nil { return nil, trace.Wrap(err) } return &e, nil case ResetPasswordTokenCreateEvent: var e events.ResetPasswordTokenCreate - if err := json.Unmarshal(data, &e); err != nil { + if err := utils.FastUnmarshal(data, &e); err != nil { return nil, trace.Wrap(err) } return &e, nil case ExecEvent: var e events.Exec - if err := json.Unmarshal(data, &e); err != nil { + if err := utils.FastUnmarshal(data, &e); err != nil { return nil, trace.Wrap(err) } return &e, nil case SubsystemEvent: var e events.Subsystem - if err := json.Unmarshal(data, &e); err != nil { + if err := utils.FastUnmarshal(data, &e); err != nil { return nil, trace.Wrap(err) } return &e, nil case X11ForwardEvent: var e events.X11Forward - if err := json.Unmarshal(data, &e); err != nil { + if err := utils.FastUnmarshal(data, &e); err != nil { return nil, trace.Wrap(err) } return &e, nil case PortForwardEvent: var e events.PortForward - if err := json.Unmarshal(data, &e); err != nil { + if err := utils.FastUnmarshal(data, &e); err != nil { return nil, trace.Wrap(err) } return &e, nil case AuthAttemptEvent: var e events.AuthAttempt - if err := json.Unmarshal(data, &e); err != nil { + if err := utils.FastUnmarshal(data, &e); err != nil { return nil, trace.Wrap(err) } return &e, nil case SCPEvent: var e events.SCP - if err := json.Unmarshal(data, &e); err != nil { + if err := utils.FastUnmarshal(data, &e); err != nil { return nil, trace.Wrap(err) } return &e, nil case ResizeEvent: var e events.Resize - if err := json.Unmarshal(data, &e); err != nil { + if err := utils.FastUnmarshal(data, &e); err != nil { return nil, trace.Wrap(err) } return &e, nil case SessionCommandEvent: var e events.SessionCommand - if err := json.Unmarshal(data, &e); err != nil { + if err := utils.FastUnmarshal(data, &e); err != nil { return nil, trace.Wrap(err) } return &e, nil case SessionDiskEvent: var e events.SessionDisk - if err := json.Unmarshal(data, &e); err != nil { + if err := utils.FastUnmarshal(data, &e); err != nil { return nil, trace.Wrap(err) } return &e, nil case SessionNetworkEvent: var e events.SessionNetwork - if err := json.Unmarshal(data, &e); err != nil { + if err := utils.FastUnmarshal(data, &e); err != nil { return nil, trace.Wrap(err) } return &e, nil case RoleCreatedEvent: var e events.RoleCreate - if err := json.Unmarshal(data, &e); err != nil { + if err := utils.FastUnmarshal(data, &e); err != nil { return nil, trace.Wrap(err) } return &e, nil case RoleDeletedEvent: var e events.RoleDelete - if err := json.Unmarshal(data, &e); err != nil { + if err := utils.FastUnmarshal(data, &e); err != nil { return nil, trace.Wrap(err) } return &e, nil case TrustedClusterCreateEvent: var e events.TrustedClusterCreate - if err := json.Unmarshal(data, &e); err != nil { + if err := utils.FastUnmarshal(data, &e); err != nil { return nil, trace.Wrap(err) } return &e, nil case TrustedClusterDeleteEvent: var e events.TrustedClusterDelete - if err := json.Unmarshal(data, &e); err != nil { + if err := utils.FastUnmarshal(data, &e); err != nil { return nil, trace.Wrap(err) } return &e, nil case TrustedClusterTokenCreateEvent: var e events.TrustedClusterTokenCreate - if err := json.Unmarshal(data, &e); err != nil { + if err := utils.FastUnmarshal(data, &e); err != nil { return nil, trace.Wrap(err) } return &e, nil case GithubConnectorCreatedEvent: var e events.GithubConnectorCreate - if err := json.Unmarshal(data, &e); err != nil { + if err := utils.FastUnmarshal(data, &e); err != nil { return nil, trace.Wrap(err) } return &e, nil case GithubConnectorDeletedEvent: var e events.GithubConnectorDelete - if err := json.Unmarshal(data, &e); err != nil { + if err := utils.FastUnmarshal(data, &e); err != nil { return nil, trace.Wrap(err) } return &e, nil case OIDCConnectorCreatedEvent: var e events.OIDCConnectorCreate - if err := json.Unmarshal(data, &e); err != nil { + if err := utils.FastUnmarshal(data, &e); err != nil { return nil, trace.Wrap(err) } return &e, nil case OIDCConnectorDeletedEvent: var e events.OIDCConnectorDelete - if err := json.Unmarshal(data, &e); err != nil { + if err := utils.FastUnmarshal(data, &e); err != nil { return nil, trace.Wrap(err) } return &e, nil case SAMLConnectorCreatedEvent: var e events.SAMLConnectorCreate - if err := json.Unmarshal(data, &e); err != nil { + if err := utils.FastUnmarshal(data, &e); err != nil { return nil, trace.Wrap(err) } return &e, nil case SAMLConnectorDeletedEvent: var e events.SAMLConnectorDelete - if err := json.Unmarshal(data, &e); err != nil { + if err := utils.FastUnmarshal(data, &e); err != nil { return nil, trace.Wrap(err) } return &e, nil case SessionRejectedEvent: var e events.SessionReject - if err := json.Unmarshal(data, &e); err != nil { + if err := utils.FastUnmarshal(data, &e); err != nil { return nil, trace.Wrap(err) } return &e, nil case AppSessionStartEvent: var e events.AppSessionStart - if err := json.Unmarshal(data, &e); err != nil { + if err := utils.FastUnmarshal(data, &e); err != nil { return nil, trace.Wrap(err) } return &e, nil case AppSessionChunkEvent: var e events.AppSessionChunk - if err := json.Unmarshal(data, &e); err != nil { + if err := utils.FastUnmarshal(data, &e); err != nil { return nil, trace.Wrap(err) } return &e, nil case AppSessionRequestEvent: var e events.AppSessionRequest - if err := json.Unmarshal(data, &e); err != nil { + if err := utils.FastUnmarshal(data, &e); err != nil { return nil, trace.Wrap(err) } return &e, nil case DatabaseSessionStartEvent: var e events.DatabaseSessionStart - if err := json.Unmarshal(data, &e); err != nil { + if err := utils.FastUnmarshal(data, &e); err != nil { return nil, trace.Wrap(err) } return &e, nil case DatabaseSessionEndEvent: var e events.DatabaseSessionEnd - if err := json.Unmarshal(data, &e); err != nil { + if err := utils.FastUnmarshal(data, &e); err != nil { return nil, trace.Wrap(err) } return &e, nil case DatabaseSessionQueryEvent: var e events.DatabaseSessionQuery - if err := json.Unmarshal(data, &e); err != nil { + if err := utils.FastUnmarshal(data, &e); err != nil { return nil, trace.Wrap(err) } return &e, nil case KubeRequestEvent: var e events.KubeRequest - if err := json.Unmarshal(data, &e); err != nil { + if err := utils.FastUnmarshal(data, &e); err != nil { return nil, trace.Wrap(err) } return &e, nil case MFADeviceAddEvent: var e events.MFADeviceAdd - if err := json.Unmarshal(data, &e); err != nil { + if err := utils.FastUnmarshal(data, &e); err != nil { return nil, trace.Wrap(err) } return &e, nil case MFADeviceDeleteEvent: var e events.MFADeviceDelete - if err := json.Unmarshal(data, &e); err != nil { + if err := utils.FastUnmarshal(data, &e); err != nil { return nil, trace.Wrap(err) } return &e, nil From 81462f3280559f78a5a11d8960b1ff5b132f3b31 Mon Sep 17 00:00:00 2001 From: xacrimon Date: Fri, 14 May 2021 18:45:26 +0200 Subject: [PATCH 21/28] feedback --- lib/events/dynamoevents/dynamoevents.go | 146 +++++------------------- lib/events/test/suite.go | 3 +- 2 files changed, 32 insertions(+), 117 deletions(-) diff --git a/lib/events/dynamoevents/dynamoevents.go b/lib/events/dynamoevents/dynamoevents.go index afd5c82c7ea38..8207dcf7ae7ae 100644 --- a/lib/events/dynamoevents/dynamoevents.go +++ b/lib/events/dynamoevents/dynamoevents.go @@ -659,136 +659,52 @@ type checkpointKey struct { // The only mandatory requirement is a date range (UTC). Results must always // show up sorted by date (newest first) func (l *Log) SearchEvents(fromUTC, toUTC time.Time, namespace string, eventTypes []string, limit int, startKey string) ([]events.AuditEvent, string, error) { - var checkpoint checkpointKey - - // If a checkpoint key is provided, unmarshal it so we can work with it's parts. - if startKey != "" { - if err := json.Unmarshal([]byte(startKey), &checkpoint); err != nil { - return nil, "", trace.Wrap(err) - } - } - - var values []events.EventFields - dates := daysBetween(fromUTC, toUTC) - query := "CreatedAtDate = :date AND CreatedAt BETWEEN :start and :end" - g := l.WithFields(log.Fields{"From": fromUTC, "To": toUTC, "Namespace": namespace, "EventTypes": eventTypes, "Limit": limit, "StartKey": startKey}) - var left int64 - if limit != 0 { - left = int64(limit) - } else { - left = math.MaxInt64 - } - doFilter := len(eventTypes) > 0 - - // Resume scanning at the correct date. We need to do this because we send individual queries per date - // and you can't resume a query with the wrong iterator checkpoint. - // - // We need to perform a guard check on the length of `dates` here in case a query is submitted with - // `toUTC` occurring before `fromUTC`. - if checkpoint.Date != "" && len(dates) > 0 { - for dates[0] != checkpoint.Date { - dates = dates[1:] - } + rawEvents, lastKey, err := l.searchEventsRaw(fromUTC, toUTC, namespace, eventTypes, limit, startKey) + if err != nil { + return nil, "", trace.Wrap(err) } - // This is the main query loop, here we send individual queries for each date and - // we stop if we hit `limit` or process all dates, whichever comes first. -dateLoop: - for _, date := range dates { - checkpoint.Date = date - - attributes := map[string]interface{}{ - ":date": date, - ":start": fromUTC.Unix(), - ":end": toUTC.Unix(), + eventArr := make([]events.AuditEvent, 0, len(rawEvents)) + for _, rawEvent := range rawEvents { + var fields events.EventFields + if err := utils.FastUnmarshal([]byte(rawEvent.Fields), fields); err != nil { + return nil, "", trace.Wrap(err) } - - attributeValues, err := dynamodbattribute.MarshalMap(attributes) + event, err := events.FromEventFields(fields) if err != nil { return nil, "", trace.Wrap(err) } - - for { - input := dynamodb.QueryInput{ - KeyConditionExpression: aws.String(query), - TableName: aws.String(l.Tablename), - ExpressionAttributeValues: attributeValues, - IndexName: aws.String(indexTimeSearchV2), - ExclusiveStartKey: checkpoint.Iterator, - Limit: aws.Int64(left), - } - - start := time.Now() - out, err := l.svc.Query(&input) - if err != nil { - return nil, "", trace.Wrap(err) - } - g.WithFields(log.Fields{"duration": time.Since(start), "items": len(out.Items)}).Debugf("Query completed.") - checkpoint.Iterator = out.LastEvaluatedKey - - for _, item := range out.Items { - var e event - if err := dynamodbattribute.UnmarshalMap(item, &e); err != nil { - return nil, "", trace.WrapWithMessage(err, "failed to unmarshal event") - } - var fields events.EventFields - data := []byte(e.Fields) - if err := json.Unmarshal(data, &fields); err != nil { - return nil, "", trace.BadParameter("failed to unmarshal event %v", err) - } - accepted := false - for i := range eventTypes { - if fields.GetString(events.EventType) == eventTypes[i] { - accepted = true - break - } - } - if accepted || !doFilter { - values = append(values, fields) - left-- - if left == 0 { - break dateLoop - } - } - } - - if len(checkpoint.Iterator) == 0 { - continue dateLoop - } - } + eventArr = append(eventArr, event) } - // When no events are left we set the checkpoint to null - if len(values) == 0 { - checkpoint = checkpointKey{} - } + sort.Sort(ByTimeAndIndex(eventArr)) + return eventArr, lastKey, nil +} - // Sort the events since Dynamo does not guarantee ordering with the used query. - sort.Sort(events.ByTimeAndIndex(values)) +// ByTimeAndIndex sorts events by time +// and if there are several session events with the same session +// by event index, regardless of the time +type ByTimeAndIndex []events.AuditEvent - var lastKey []byte - var err error - - if len(values) > 0 { - lastKey, err = json.Marshal(&checkpoint) - if err != nil { - return nil, "", trace.Wrap(err) - } - } +func (f ByTimeAndIndex) Len() int { + return len(f) +} - eventArr := make([]events.AuditEvent, 0, len(values)) - for _, fields := range values { - event, err := events.FromEventFields(fields) - if err != nil { - return nil, "", trace.Wrap(err) - } - eventArr = append(eventArr, event) +func (f ByTimeAndIndex) Less(i, j int) bool { + itime := f[i].GetTime() + jtime := f[j].GetTime() + if itime.Equal(jtime) && events.GetSessionID(f[i]) == events.GetSessionID(f[j]) { + return f[i].GetIndex() < f[j].GetIndex() } + return itime.Before(jtime) +} - return eventArr, string(lastKey), nil +func (f ByTimeAndIndex) Swap(i, j int) { + f[i], f[j] = f[j], f[i] } -// Used in tests to check full backend data. +// searchEventsRaw is a low level function for searching for events. This is kept +// seperate from the SearchEvents function in order to allow tests to grab more metadata. func (l *Log) searchEventsRaw(fromUTC, toUTC time.Time, namespace string, eventTypes []string, limit int, startKey string) ([]event, string, error) { var checkpoint checkpointKey diff --git a/lib/events/test/suite.go b/lib/events/test/suite.go index c3af2a1acc6ba..2a5b524bc8f71 100644 --- a/lib/events/test/suite.go +++ b/lib/events/test/suite.go @@ -81,9 +81,8 @@ type EventsSuite struct { // EventPagination covers event search pagination. func (s *EventsSuite) EventPagination(c *check.C) { - // 2019-05-10 14:43:0 GMT // This serves no special purpose except to make querying easier. - baseTime := time.Unix(1557499380, 0) + baseTime := time.Date(2019, time.May, 10, 14, 43, 0, 0, time.UTC) names := []string{"bob", "jack", "daisy", "evan"} From b378e235089607c838b0c462c9c2ee243f082905 Mon Sep 17 00:00:00 2001 From: xacrimon Date: Fri, 14 May 2021 19:05:45 +0200 Subject: [PATCH 22/28] embed iauditlog in forward to get rid of wrappers --- lib/auth/tls_test.go | 4 +-- lib/events/auditlog_test.go | 6 ++-- lib/events/forward.go | 58 ++++--------------------------------- lib/events/recorder.go | 2 +- 4 files changed, 11 insertions(+), 59 deletions(-) diff --git a/lib/auth/tls_test.go b/lib/auth/tls_test.go index c36e3bcda4eb1..7cf4eaaadd9fb 100644 --- a/lib/auth/tls_test.go +++ b/lib/auth/tls_test.go @@ -1255,7 +1255,7 @@ func (s *TLSSuite) TestSharedSessions(c *check.C) { ServerID: teleport.ComponentUpload, DataDir: uploadDir, RecordSessions: true, - ForwardTo: clt, + IAuditLog: clt, }) c.Assert(err, check.IsNil) @@ -1288,7 +1288,7 @@ func (s *TLSSuite) TestSharedSessions(c *check.C) { ServerID: teleport.ComponentUpload, DataDir: uploadDir, RecordSessions: true, - ForwardTo: clt, + IAuditLog: clt, }) c.Assert(err, check.IsNil) err = clt.PostSessionSlice(events.SessionSlice{ diff --git a/lib/events/auditlog_test.go b/lib/events/auditlog_test.go index 0b9343d6f93d6..6375f18a62de8 100644 --- a/lib/events/auditlog_test.go +++ b/lib/events/auditlog_test.go @@ -134,7 +134,7 @@ func (a *AuditTestSuite) TestSessionsOnOneAuthServer(c *check.C) { ServerID: teleport.ComponentUpload, DataDir: uploadDir, RecordSessions: true, - ForwardTo: alog, + IAuditLog: alog, Clock: fakeClock, }) c.Assert(err, check.IsNil) @@ -254,7 +254,7 @@ func (a *AuditTestSuite) TestSessionRecordingOff(c *check.C) { ServerID: teleport.ComponentUpload, DataDir: uploadDir, RecordSessions: false, - ForwardTo: alog, + IAuditLog: alog, Clock: fakeClock, }) c.Assert(err, check.IsNil) @@ -499,7 +499,7 @@ func (a *AuditTestSuite) forwardAndUpload(c *check.C, fakeClock clockwork.Clock, ServerID: "upload", DataDir: uploadDir, RecordSessions: true, - ForwardTo: alog, + IAuditLog: alog, }) c.Assert(err, check.IsNil) diff --git a/lib/events/forward.go b/lib/events/forward.go index 2c60b71f07e43..ad4c8ff67fc7f 100644 --- a/lib/events/forward.go +++ b/lib/events/forward.go @@ -17,7 +17,6 @@ limitations under the License. package events import ( - "context" "encoding/json" "sync" "time" @@ -32,6 +31,9 @@ import ( // ForwarderConfig forwards session log events // to the auth server, and writes the session playback to disk type ForwarderConfig struct { + // IAuditLog is the audit log to forward non-print events to + IAuditLog + // SessionID is a session id to write SessionID session.ID // ServerID is a serverID data directory @@ -42,8 +44,6 @@ type ForwarderConfig struct { RecordSessions bool // Namespace is a namespace of the session Namespace string - // ForwardTo is the audit log to forward non-print events to - ForwardTo IAuditLog // Clock is a clock to set for tests Clock clockwork.Clock // UID is UID generator @@ -52,7 +52,7 @@ type ForwarderConfig struct { // CheckAndSetDefaults checks and sets default values func (s *ForwarderConfig) CheckAndSetDefaults() error { - if s.ForwardTo == nil { + if s.IAuditLog == nil { return trace.BadParameter("missing parameter bucket") } if s.DataDir == "" { @@ -163,7 +163,7 @@ func (l *Forwarder) PostSessionSlice(slice SessionSlice) error { } slice.Chunks = chunksWithoutPrintEvents slice.Version = V3 - err = l.ForwardTo.PostSessionSlice(slice) + err = l.IAuditLog.PostSessionSlice(slice) return err } @@ -209,51 +209,3 @@ func (l *Forwarder) setupSlice(slice *SessionSlice) ([]*SessionChunk, error) { return chunks, nil } - -// UploadSessionRecording uploads session recording to the audit server -func (l *Forwarder) UploadSessionRecording(r SessionRecording) error { - return l.ForwardTo.UploadSessionRecording(r) -} - -// GetSessionChunk returns a reader which can be used to read a byte stream -// of a recorded session starting from 'offsetBytes' (pass 0 to start from the -// beginning) up to maxBytes bytes. -// -// If maxBytes > MaxChunkBytes, it gets rounded down to MaxChunkBytes -func (l *Forwarder) GetSessionChunk(namespace string, sid session.ID, offsetBytes, maxBytes int) ([]byte, error) { - return l.ForwardTo.GetSessionChunk(namespace, sid, offsetBytes, maxBytes) -} - -// Returns all events that happen during a session sorted by time -// (oldest first). -// -// after tells to use only return events after a specified cursor Id -// -// This function is usually used in conjunction with GetSessionReader to -// replay recorded session streams. -func (l *Forwarder) GetSessionEvents(namespace string, sid session.ID, after int, includePrintEvents bool) ([]EventFields, error) { - return l.ForwardTo.GetSessionEvents(namespace, sid, after, includePrintEvents) -} - -// SearchEvents is a flexible way to find events. -// -// Event types to filter can be specified and pagination is handled by an iterator key that allows -// a query to be resumed. -// -// The only mandatory requirement is a date range (UTC). Results must always -// show up sorted by date (newest first) -func (l *Forwarder) SearchEvents(fromUTC, toUTC time.Time, namespace string, eventType []string, limit int, startKey string) ([]AuditEvent, string, error) { - return l.ForwardTo.SearchEvents(fromUTC, toUTC, namespace, eventType, limit, startKey) -} - -// SearchSessionEvents returns session related events only. This is used to -// find completed session. -func (l *Forwarder) SearchSessionEvents(fromUTC time.Time, toUTC time.Time, limit int, startKey string) ([]AuditEvent, string, error) { - return l.ForwardTo.SearchSessionEvents(fromUTC, toUTC, limit, startKey) -} - -// WaitForDelivery waits for resources to be released and outstanding requests to -// complete after calling Close method -func (l *Forwarder) WaitForDelivery(ctx context.Context) error { - return l.ForwardTo.WaitForDelivery(ctx) -} diff --git a/lib/events/recorder.go b/lib/events/recorder.go index 94ae7ce6c780e..7821755cbb147 100644 --- a/lib/events/recorder.go +++ b/lib/events/recorder.go @@ -121,7 +121,7 @@ func NewForwardRecorder(cfg ForwardRecorderConfig) (*ForwardRecorder, error) { DataDir: cfg.DataDir, RecordSessions: cfg.RecordSessions, Namespace: cfg.Namespace, - ForwardTo: cfg.ForwardTo, + IAuditLog: cfg.ForwardTo, }) if err != nil { return nil, trace.Wrap(err) From 7ffce9086423045b2bdad97dbca17c594dd2d6fd Mon Sep 17 00:00:00 2001 From: xacrimon Date: Fri, 14 May 2021 19:09:16 +0200 Subject: [PATCH 23/28] fix typo --- lib/events/dynamoevents/dynamoevents.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/events/dynamoevents/dynamoevents.go b/lib/events/dynamoevents/dynamoevents.go index 8207dcf7ae7ae..5be04dfd24de5 100644 --- a/lib/events/dynamoevents/dynamoevents.go +++ b/lib/events/dynamoevents/dynamoevents.go @@ -704,7 +704,7 @@ func (f ByTimeAndIndex) Swap(i, j int) { } // searchEventsRaw is a low level function for searching for events. This is kept -// seperate from the SearchEvents function in order to allow tests to grab more metadata. +// separate from the SearchEvents function in order to allow tests to grab more metadata. func (l *Log) searchEventsRaw(fromUTC, toUTC time.Time, namespace string, eventTypes []string, limit int, startKey string) ([]event, string, error) { var checkpoint checkpointKey From 82e019bbb2a83464968553a415bc871482ce7a9f Mon Sep 17 00:00:00 2001 From: xacrimon Date: Fri, 14 May 2021 19:54:14 +0200 Subject: [PATCH 24/28] fix json error due to non pointer --- lib/events/dynamic.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/events/dynamic.go b/lib/events/dynamic.go index 5492ba7e71576..50ecc77e5bf5e 100644 --- a/lib/events/dynamic.go +++ b/lib/events/dynamic.go @@ -360,7 +360,7 @@ func GetSessionID(event AuditEvent) string { // with existing public API routes when the backend is updated with the typed events. func ToEventFields(event AuditEvent) (EventFields, error) { var fields EventFields - if err := utils.ObjectToStruct(event, fields); err != nil { + if err := utils.ObjectToStruct(event, &fields); err != nil { return nil, trace.Wrap(err) } From 8ca3e9c305e44f503553ad879cf2e3426482328d Mon Sep 17 00:00:00 2001 From: xacrimon Date: Fri, 14 May 2021 20:08:16 +0200 Subject: [PATCH 25/28] fix test --- lib/events/auditlog_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/events/auditlog_test.go b/lib/events/auditlog_test.go index 6375f18a62de8..761d009454c72 100644 --- a/lib/events/auditlog_test.go +++ b/lib/events/auditlog_test.go @@ -357,6 +357,7 @@ func (a *AuditTestSuite) TestLogRotation(c *check.C) { // emit regular event: event := &events.Resize{ + Metadata: events.Metadata{Type: "resize"}, TerminalSize: "10:10", } err = alog.EmitAuditEvent(context.TODO(), event) From 82cad404e31b52910d474b10a07b5c11245a257d Mon Sep 17 00:00:00 2001 From: xacrimon Date: Fri, 14 May 2021 20:25:12 +0200 Subject: [PATCH 26/28] expand pagination test --- lib/events/test/suite.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/lib/events/test/suite.go b/lib/events/test/suite.go index 2a5b524bc8f71..fca121e28c3ec 100644 --- a/lib/events/test/suite.go +++ b/lib/events/test/suite.go @@ -109,6 +109,21 @@ func (s *EventsSuite) EventPagination(c *check.C) { c.Assert(ok, check.Equals, true) c.Assert(name, check.Equals, event.User) } + + checkpoint = "" + for i := range []int{0, 2} { + nameA := names[i] + nameB := names[i+1] + arr, checkpoint, err = s.Log.SearchEvents(baseTime, toTime, defaults.Namespace, nil, 2, checkpoint) + c.Assert(err, check.IsNil) + c.Assert(arr, check.HasLen, 2) + eventA, okA := arr[0].(*events.UserLogin) + eventB, okB := arr[1].(*events.UserLogin) + c.Assert(okA, check.Equals, true) + c.Assert(okB, check.Equals, true) + c.Assert(nameA, check.Equals, eventA.User) + c.Assert(nameB, check.Equals, eventB.User) + } } // SessionEventsCRUD covers session events From 6d1162e8d23350998184ca40922900d8f199db52 Mon Sep 17 00:00:00 2001 From: xacrimon Date: Mon, 17 May 2021 23:22:06 +0200 Subject: [PATCH 27/28] feedback from andrej --- api/client/proto/authservice.proto | 11 +++++++---- lib/events/dynamoevents/dynamoevents.go | 13 ++++++------- lib/web/apiserver.go | 6 +++--- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/api/client/proto/authservice.proto b/api/client/proto/authservice.proto index dd4560bbb89cb..40edb2163c1d2 100644 --- a/api/client/proto/authservice.proto +++ b/api/client/proto/authservice.proto @@ -818,17 +818,19 @@ message SingleUseUserCert { message GetEventsRequest { // Namespace, if not set, defaults to 'default' string Namespace = 1; - // Oldest date of returned events + // StartDate is the oldest date of returned events google.protobuf.Timestamp StartDate = 2 [ (gogoproto.stdtime) = true, (gogoproto.nullable) = false ]; - // Newest date of returned events + // EndDate is the newest date of returned events google.protobuf.Timestamp EndDate = 3 [ (gogoproto.stdtime) = true, (gogoproto.nullable) = false ]; // EventTypes is optional, if not set, returns all events repeated string EventTypes = 4; - // Maximum amount of events returned + // Limit is the maximum amount of events returned int32 Limit = 5; - // When supplied the search will resume from the last key + // StartKey is used to resume a query in order to enable pagination. + // If the previous response had LastKey set then this should be + // set to its value. Otherwise leave empty. string StartKey = 6; } @@ -848,6 +850,7 @@ message GetSessionEventsRequest { } message Events { + // Items is a list of typed gRPC formatted audit events. repeated events.OneOf Items = 1; // the key of the last event if the returned set did not contain all events found i.e limit < // actual amount. this is the key clients can supply in another API request to continue fetching diff --git a/lib/events/dynamoevents/dynamoevents.go b/lib/events/dynamoevents/dynamoevents.go index 5be04dfd24de5..32f126d84efb7 100644 --- a/lib/events/dynamoevents/dynamoevents.go +++ b/lib/events/dynamoevents/dynamoevents.go @@ -677,20 +677,19 @@ func (l *Log) SearchEvents(fromUTC, toUTC time.Time, namespace string, eventType eventArr = append(eventArr, event) } - sort.Sort(ByTimeAndIndex(eventArr)) + sort.Sort(byTimeAndIndex(eventArr)) return eventArr, lastKey, nil } // ByTimeAndIndex sorts events by time -// and if there are several session events with the same session -// by event index, regardless of the time -type ByTimeAndIndex []events.AuditEvent +// and if there are several session events with the same session by event index. +type byTimeAndIndex []events.AuditEvent -func (f ByTimeAndIndex) Len() int { +func (f byTimeAndIndex) Len() int { return len(f) } -func (f ByTimeAndIndex) Less(i, j int) bool { +func (f byTimeAndIndex) Less(i, j int) bool { itime := f[i].GetTime() jtime := f[j].GetTime() if itime.Equal(jtime) && events.GetSessionID(f[i]) == events.GetSessionID(f[j]) { @@ -699,7 +698,7 @@ func (f ByTimeAndIndex) Less(i, j int) bool { return itime.Before(jtime) } -func (f ByTimeAndIndex) Swap(i, j int) { +func (f byTimeAndIndex) Swap(i, j int) { f[i], f[j] = f[j], f[i] } diff --git a/lib/web/apiserver.go b/lib/web/apiserver.go index a875614ea1058..983f9ff677285 100644 --- a/lib/web/apiserver.go +++ b/lib/web/apiserver.go @@ -1864,7 +1864,7 @@ func (h *Handler) siteSessionGet(w http.ResponseWriter, r *http.Request, p httpr const maxStreamBytes = 5 * 1024 * 1024 -func toArrayFields(rawEvents []events.AuditEvent) ([]events.EventFields, error) { +func toFieldsSlice(rawEvents []events.AuditEvent) ([]events.EventFields, error) { el := make([]events.EventFields, 0, len(rawEvents)) for _, event := range rawEvents { els, err := events.ToEventFields(event) @@ -1922,7 +1922,7 @@ func (h *Handler) clusterSearchSessionEvents(w http.ResponseWriter, r *http.Requ return nil, trace.Wrap(err) } - el, err := toArrayFields(rawEvents) + el, err := toFieldsSlice(rawEvents) if err != nil { return nil, trace.Wrap(err) } @@ -1970,7 +1970,7 @@ func (h *Handler) clusterSearchEvents(w http.ResponseWriter, r *http.Request, p return nil, trace.Wrap(err) } - el, err := toArrayFields(rawEvents) + el, err := toFieldsSlice(rawEvents) if err != nil { return nil, trace.Wrap(err) } From 377f74e551aff06f9bff4b767478aa4d3e2ff437 Mon Sep 17 00:00:00 2001 From: xacrimon Date: Mon, 17 May 2021 23:46:02 +0200 Subject: [PATCH 28/28] update proto --- api/client/proto/authservice.pb.go | 31 ++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/api/client/proto/authservice.pb.go b/api/client/proto/authservice.pb.go index 5bdd932c6e166..2ec8e5dfffb98 100644 --- a/api/client/proto/authservice.pb.go +++ b/api/client/proto/authservice.pb.go @@ -5907,15 +5907,17 @@ func (*SingleUseUserCert) XXX_OneofWrappers() []interface{} { type GetEventsRequest struct { // Namespace, if not set, defaults to 'default' Namespace string `protobuf:"bytes,1,opt,name=Namespace,proto3" json:"Namespace,omitempty"` - // Oldest date of returned events + // StartDate is the oldest date of returned events StartDate time.Time `protobuf:"bytes,2,opt,name=StartDate,proto3,stdtime" json:"StartDate"` - // Newest date of returned events + // EndDate is the newest date of returned events EndDate time.Time `protobuf:"bytes,3,opt,name=EndDate,proto3,stdtime" json:"EndDate"` // EventTypes is optional, if not set, returns all events EventTypes []string `protobuf:"bytes,4,rep,name=EventTypes,proto3" json:"EventTypes,omitempty"` - // Maximum amount of events returned + // Limit is the maximum amount of events returned Limit int32 `protobuf:"varint,5,opt,name=Limit,proto3" json:"Limit,omitempty"` - // When supplied the search will resume from the last key + // StartKey is used to resume a query in order to enable pagination. + // If the previous response had LastKey set then this should be + // set to its value. Otherwise leave empty. StartKey string `protobuf:"bytes,6,opt,name=StartKey,proto3" json:"StartKey,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` @@ -5998,15 +6000,19 @@ func (m *GetEventsRequest) GetStartKey() string { } type GetSessionEventsRequest struct { - // Oldest date of returned events + // StartDate is the oldest date of returned events StartDate time.Time `protobuf:"bytes,1,opt,name=StartDate,proto3,stdtime" json:"StartDate"` - // Newest date of returned events - EndDate time.Time `protobuf:"bytes,2,opt,name=EndDate,proto3,stdtime" json:"EndDate"` - Limit int32 `protobuf:"varint,3,opt,name=Limit,proto3" json:"Limit,omitempty"` - StartKey string `protobuf:"bytes,4,opt,name=StartKey,proto3" json:"StartKey,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + // EndDate is the newest date of returned events + EndDate time.Time `protobuf:"bytes,2,opt,name=EndDate,proto3,stdtime" json:"EndDate"` + // Limit is the maximum amount of events to retrieve. + Limit int32 `protobuf:"varint,3,opt,name=Limit,proto3" json:"Limit,omitempty"` + // StartKey is used to resume a query in order to enable pagination. + // If the previous response had LastKey set then this should be + // set to its value. Otherwise leave empty. + StartKey string `protobuf:"bytes,4,opt,name=StartKey,proto3" json:"StartKey,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *GetSessionEventsRequest) Reset() { *m = GetSessionEventsRequest{} } @@ -6071,6 +6077,7 @@ func (m *GetSessionEventsRequest) GetStartKey() string { } type Events struct { + // Items is a list of typed gRPC formatted audit events. Items []*events.OneOf `protobuf:"bytes,1,rep,name=Items,proto3" json:"Items,omitempty"` // the key of the last event if the returned set did not contain all events found i.e limit < // actual amount. this is the key clients can supply in another API request to continue fetching