From 1b2c20e07cf33a436f52c8f6d034af55d06e146c Mon Sep 17 00:00:00 2001 From: Mark Gritter Date: Tue, 8 Sep 2020 14:22:09 -0500 Subject: [PATCH] Merge activity log work to date on enterprise back into oss. (#9900) * Added stub class for activity logging. (#1435) * Define activity fragments and starter methods for manipulating them. (#1441) --- Makefile | 3 +- vault/activity/activity_log.pb.go | 278 ++++++++++++++++++++++++++++++ vault/activity/activity_log.proto | 28 +++ vault/activity_log.go | 108 ++++++++++++ vault/activity_log_test.go | 76 ++++++++ vault/core.go | 6 + 6 files changed, 498 insertions(+), 1 deletion(-) create mode 100644 vault/activity/activity_log.pb.go create mode 100644 vault/activity/activity_log.proto create mode 100644 vault/activity_log.go create mode 100644 vault/activity_log_test.go diff --git a/Makefile b/Makefile index 80f04ceaa..d7e0add70 100644 --- a/Makefile +++ b/Makefile @@ -197,6 +197,7 @@ static-dist-dev: ember-dist-dev static-assets proto: protoc vault/*.proto --go_out=plugins=grpc,paths=source_relative:. + protoc vault/activity/activity_log.proto --go_out=plugins=grpc,paths=source_relative:. protoc helper/storagepacker/types.proto --go_out=plugins=grpc,paths=source_relative:. protoc helper/forwarding/types.proto --go_out=plugins=grpc,paths=source_relative:. protoc sdk/logical/*.proto --go_out=plugins=grpc,paths=source_relative:. @@ -207,7 +208,7 @@ proto: protoc sdk/database/newdbplugin/proto/*.proto --go_out=plugins=grpc,paths=source_relative:. protoc sdk/plugin/pb/*.proto --go_out=plugins=grpc,paths=source_relative:. sed -i -e 's/Id/ID/' vault/request_forwarding_service.pb.go - sed -i -e 's/Idp/IDP/' -e 's/Url/URL/' -e 's/Id/ID/' -e 's/IDentity/Identity/' -e 's/EntityId/EntityID/' -e 's/Api/API/' -e 's/Qr/QR/' -e 's/Totp/TOTP/' -e 's/Mfa/MFA/' -e 's/Pingid/PingID/' -e 's/protobuf:"/sentinel:"" protobuf:"/' -e 's/namespaceId/namespaceID/' -e 's/Ttl/TTL/' -e 's/BoundCidrs/BoundCIDRs/' helper/identity/types.pb.go helper/identity/mfa/types.pb.go helper/storagepacker/types.pb.go sdk/plugin/pb/backend.pb.go sdk/logical/identity.pb.go + sed -i -e 's/Idp/IDP/' -e 's/Url/URL/' -e 's/Id/ID/' -e 's/IDentity/Identity/' -e 's/EntityId/EntityID/' -e 's/Api/API/' -e 's/Qr/QR/' -e 's/Totp/TOTP/' -e 's/Mfa/MFA/' -e 's/Pingid/PingID/' -e 's/protobuf:"/sentinel:"" protobuf:"/' -e 's/namespaceId/namespaceID/' -e 's/Ttl/TTL/' -e 's/BoundCidrs/BoundCIDRs/' helper/identity/types.pb.go helper/identity/mfa/types.pb.go helper/storagepacker/types.pb.go sdk/plugin/pb/backend.pb.go sdk/logical/identity.pb.go vault/activity/activity_log.pb.go fmtcheck: @true diff --git a/vault/activity/activity_log.pb.go b/vault/activity/activity_log.pb.go new file mode 100644 index 000000000..d101abbe7 --- /dev/null +++ b/vault/activity/activity_log.pb.go @@ -0,0 +1,278 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.22.0 +// protoc v3.6.1 +// source: vault/activity/activity_log.proto + +package activity + +import ( + proto "github.com/golang/protobuf/proto" + _ "github.com/golang/protobuf/ptypes/timestamp" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// This is a compile-time assertion that a sufficiently up-to-date version +// of the legacy proto package is being used. +const _ = proto.ProtoPackageIsVersion4 + +// EntityRecord is generated the first time an entity is active +// each month. +type EntityRecord struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + EntityID string `sentinel:"" protobuf:"bytes,1,opt,name=entity_id,json=entityId,proto3" json:"entity_id,omitempty"` + NamespaceID string `sentinel:"" protobuf:"bytes,2,opt,name=namespace_id,json=namespaceID,proto3" json:"namespace_id,omitempty"` + // using the Timestamp type would cost us an extra + // 4 bytes per record to store nanoseconds. + Timestamp int64 `sentinel:"" protobuf:"varint,3,opt,name=timestamp,proto3" json:"timestamp,omitempty"` +} + +func (x *EntityRecord) Reset() { + *x = EntityRecord{} + if protoimpl.UnsafeEnabled { + mi := &file_vault_activity_activity_log_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *EntityRecord) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EntityRecord) ProtoMessage() {} + +func (x *EntityRecord) ProtoReflect() protoreflect.Message { + mi := &file_vault_activity_activity_log_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EntityRecord.ProtoReflect.Descriptor instead. +func (*EntityRecord) Descriptor() ([]byte, []int) { + return file_vault_activity_activity_log_proto_rawDescGZIP(), []int{0} +} + +func (x *EntityRecord) GetEntityID() string { + if x != nil { + return x.EntityID + } + return "" +} + +func (x *EntityRecord) GetNamespaceID() string { + if x != nil { + return x.NamespaceID + } + return "" +} + +func (x *EntityRecord) GetTimestamp() int64 { + if x != nil { + return x.Timestamp + } + return 0 +} + +type LogFragment struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // hostname (or node ID?) where the fragment originated, + // used for debugging. + OriginatingNode string `sentinel:"" protobuf:"bytes,1,opt,name=originating_node,json=originatingNode,proto3" json:"originating_node,omitempty"` + // active entities not yet in a log segment + Entities []*EntityRecord `sentinel:"" protobuf:"bytes,2,rep,name=entities,proto3" json:"entities,omitempty"` + // token counts not yet in a log segment, + // indexed by namespace ID + NonEntityTokens map[string]uint64 `sentinel:"" protobuf:"bytes,3,rep,name=non_entity_tokens,json=nonEntityTokens,proto3" json:"non_entity_tokens,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3"` +} + +func (x *LogFragment) Reset() { + *x = LogFragment{} + if protoimpl.UnsafeEnabled { + mi := &file_vault_activity_activity_log_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *LogFragment) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LogFragment) ProtoMessage() {} + +func (x *LogFragment) ProtoReflect() protoreflect.Message { + mi := &file_vault_activity_activity_log_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LogFragment.ProtoReflect.Descriptor instead. +func (*LogFragment) Descriptor() ([]byte, []int) { + return file_vault_activity_activity_log_proto_rawDescGZIP(), []int{1} +} + +func (x *LogFragment) GetOriginatingNode() string { + if x != nil { + return x.OriginatingNode + } + return "" +} + +func (x *LogFragment) GetEntities() []*EntityRecord { + if x != nil { + return x.Entities + } + return nil +} + +func (x *LogFragment) GetNonEntityTokens() map[string]uint64 { + if x != nil { + return x.NonEntityTokens + } + return nil +} + +var File_vault_activity_activity_log_proto protoreflect.FileDescriptor + +var file_vault_activity_activity_log_proto_rawDesc = []byte{ + 0x0a, 0x21, 0x76, 0x61, 0x75, 0x6c, 0x74, 0x2f, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, + 0x2f, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x5f, 0x6c, 0x6f, 0x67, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x1a, 0x1f, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, + 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x6c, + 0x0a, 0x0c, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x12, 0x1b, + 0x0a, 0x09, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x08, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x6e, + 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0b, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x49, 0x64, 0x12, 0x1c, + 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x22, 0x88, 0x02, 0x0a, + 0x0b, 0x4c, 0x6f, 0x67, 0x46, 0x72, 0x61, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x29, 0x0a, 0x10, + 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x6e, 0x6f, 0x64, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x74, + 0x69, 0x6e, 0x67, 0x4e, 0x6f, 0x64, 0x65, 0x12, 0x32, 0x0a, 0x08, 0x65, 0x6e, 0x74, 0x69, 0x74, + 0x69, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x61, 0x63, 0x74, 0x69, + 0x76, 0x69, 0x74, 0x79, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, 0x63, 0x6f, 0x72, + 0x64, 0x52, 0x08, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x69, 0x65, 0x73, 0x12, 0x56, 0x0a, 0x11, 0x6e, + 0x6f, 0x6e, 0x5f, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, + 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, + 0x79, 0x2e, 0x4c, 0x6f, 0x67, 0x46, 0x72, 0x61, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x4e, 0x6f, + 0x6e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x52, 0x0f, 0x6e, 0x6f, 0x6e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x54, 0x6f, 0x6b, + 0x65, 0x6e, 0x73, 0x1a, 0x42, 0x0a, 0x14, 0x4e, 0x6f, 0x6e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, + 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, + 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x2b, 0x5a, 0x29, 0x67, 0x69, 0x74, 0x68, 0x75, + 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, + 0x76, 0x61, 0x75, 0x6c, 0x74, 0x2f, 0x76, 0x61, 0x75, 0x6c, 0x74, 0x2f, 0x61, 0x63, 0x74, 0x69, + 0x76, 0x69, 0x74, 0x79, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_vault_activity_activity_log_proto_rawDescOnce sync.Once + file_vault_activity_activity_log_proto_rawDescData = file_vault_activity_activity_log_proto_rawDesc +) + +func file_vault_activity_activity_log_proto_rawDescGZIP() []byte { + file_vault_activity_activity_log_proto_rawDescOnce.Do(func() { + file_vault_activity_activity_log_proto_rawDescData = protoimpl.X.CompressGZIP(file_vault_activity_activity_log_proto_rawDescData) + }) + return file_vault_activity_activity_log_proto_rawDescData +} + +var file_vault_activity_activity_log_proto_msgTypes = make([]protoimpl.MessageInfo, 3) +var file_vault_activity_activity_log_proto_goTypes = []interface{}{ + (*EntityRecord)(nil), // 0: activity.EntityRecord + (*LogFragment)(nil), // 1: activity.LogFragment + nil, // 2: activity.LogFragment.NonEntityTokensEntry +} +var file_vault_activity_activity_log_proto_depIDxs = []int32{ + 0, // 0: activity.LogFragment.entities:type_name -> activity.EntityRecord + 2, // 1: activity.LogFragment.non_entity_tokens:type_name -> activity.LogFragment.NonEntityTokensEntry + 2, // [2:2] is the sub-list for method output_type + 2, // [2:2] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name +} + +func init() { file_vault_activity_activity_log_proto_init() } +func file_vault_activity_activity_log_proto_init() { + if File_vault_activity_activity_log_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_vault_activity_activity_log_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*EntityRecord); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_vault_activity_activity_log_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*LogFragment); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_vault_activity_activity_log_proto_rawDesc, + NumEnums: 0, + NumMessages: 3, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_vault_activity_activity_log_proto_goTypes, + DependencyIndexes: file_vault_activity_activity_log_proto_depIDxs, + MessageInfos: file_vault_activity_activity_log_proto_msgTypes, + }.Build() + File_vault_activity_activity_log_proto = out.File + file_vault_activity_activity_log_proto_rawDesc = nil + file_vault_activity_activity_log_proto_goTypes = nil + file_vault_activity_activity_log_proto_depIDxs = nil +} diff --git a/vault/activity/activity_log.proto b/vault/activity/activity_log.proto new file mode 100644 index 000000000..a3acd6cf5 --- /dev/null +++ b/vault/activity/activity_log.proto @@ -0,0 +1,28 @@ +syntax = "proto3"; + +option go_package = "github.com/hashicorp/vault/vault/activity"; + +package activity; + +// EntityRecord is generated the first time an entity is active +// each month. +message EntityRecord { + string entity_id = 1; + string namespace_id = 2; + // using the Timestamp type would cost us an extra + // 4 bytes per record to store nanoseconds. + int64 timestamp = 3; +} + +message LogFragment { + // hostname (or node ID?) where the fragment originated, + // used for debugging. + string originating_node = 1; + + // active entities not yet in a log segment + repeated EntityRecord entities = 2; + + // token counts not yet in a log segment, + // indexed by namespace ID + map non_entity_tokens = 3; +} diff --git a/vault/activity_log.go b/vault/activity_log.go new file mode 100644 index 000000000..a21d194ec --- /dev/null +++ b/vault/activity_log.go @@ -0,0 +1,108 @@ +package vault + +import ( + "context" + "os" + "sync" + "time" + + log "github.com/hashicorp/go-hclog" + "github.com/hashicorp/vault/sdk/logical" + "github.com/hashicorp/vault/vault/activity" +) + +const ( + // activitySubPath is the directory under the system view where + // the log will be stored. + activitySubPath = "activity/" +) + +// ActivityLog tracks unique entity counts and non-entity token counts. +// It handles assembling log fragments (and sending them to the active +// node), writing log segments, and precomputing queries. +type ActivityLog struct { + // log destination + logger log.Logger + + // view is the storage location used by ActivityLog, + // defaults to sys/activity. + view logical.Storage + + // nodeID is the ID to use for all fragments that + // are generated. + // TODO: use secondary ID when available? + nodeID string + + // current log fragment (may be nil) and a mutex to protect it + fragmentLock sync.RWMutex + fragment *activity.LogFragment + fragmentCreation time.Time +} + +// NewActivityLog creates an activity log. +func NewActivityLog(_ context.Context, logger log.Logger, view logical.Storage) (*ActivityLog, error) { + hostname, err := os.Hostname() + if err != nil { + return nil, err + } + + return &ActivityLog{ + logger: logger, + view: view, + nodeID: hostname, + }, nil +} + +// setupActivityLog hooks up the singleton ActivityLog into Core. +func (c *Core) setupActivityLog(ctx context.Context) error { + view := c.systemBarrierView.SubView(activitySubPath) + logger := c.baseLogger.Named("activity") + c.AddLogger(logger) + + manager, err := NewActivityLog(ctx, logger, view) + if err != nil { + return err + } + c.activityLog = manager + return nil +} + +func (a *ActivityLog) AddEntityToFragment(entityID string, namespaceID string, timestamp time.Time) { + a.fragmentLock.Lock() + defer a.fragmentLock.Unlock() + + // TODO: check whether entity ID already recorded + + a.createCurrentFragment() + + a.fragment.Entities = append(a.fragment.Entities, + &activity.EntityRecord{ + EntityID: entityID, + NamespaceID: namespaceID, + Timestamp: timestamp.UnixNano(), + }) +} + +func (a *ActivityLog) AddTokenToFragment(namespaceID string) { + a.fragmentLock.Lock() + defer a.fragmentLock.Unlock() + + a.createCurrentFragment() + + a.fragment.NonEntityTokens[namespaceID] += 1 +} + +// Create the current fragment if it doesn't already exist. +// Must be called with the lock held. +func (a *ActivityLog) createCurrentFragment() { + if a.fragment == nil { + a.fragment = &activity.LogFragment{ + OriginatingNode: a.nodeID, + Entities: make([]*activity.EntityRecord, 0, 120), + NonEntityTokens: make(map[string]uint64), + } + a.fragmentCreation = time.Now() + + // TODO: start a timer to send it, if we're a performance standby + } +} diff --git a/vault/activity_log_test.go b/vault/activity_log_test.go new file mode 100644 index 000000000..954c1dca6 --- /dev/null +++ b/vault/activity_log_test.go @@ -0,0 +1,76 @@ +package vault + +import ( + "testing" + "time" +) + +func TestActivityLog_Creation(t *testing.T) { + core, _, _ := TestCoreUnsealed(t) + + a := core.activityLog + + if a == nil { + t.Fatal("no activity log found") + } + if a.logger == nil || a.view == nil { + t.Fatal("activity log not initialized") + } + if a.fragment != nil { + t.Fatal("activity log already has fragment") + } + + const entity_id = "entity_id_75432" + const namespace_id = "ns123" + ts := time.Now() + + a.AddEntityToFragment(entity_id, namespace_id, ts) + if a.fragment == nil { + t.Fatal("no fragment created") + } + + if a.fragment.OriginatingNode != a.nodeID { + t.Errorf("mismatched node ID, %q vs %q", a.fragment.OriginatingNode, a.nodeID) + } + + if a.fragment.Entities == nil { + t.Fatal("no fragment entity slice") + } + + if a.fragment.NonEntityTokens == nil { + t.Fatal("no fragment token map") + } + + if len(a.fragment.Entities) != 1 { + t.Fatalf("wrong number of entities %v", len(a.fragment.Entities)) + } + + er := a.fragment.Entities[0] + if er.EntityID != entity_id { + t.Errorf("mimatched entity ID, %q vs %q", er.EntityID, entity_id) + } + if er.NamespaceID != namespace_id { + t.Errorf("mimatched namespace ID, %q vs %q", er.NamespaceID, namespace_id) + } + if er.Timestamp != ts.UnixNano() { + t.Errorf("mimatched timestamp, %v vs %v", er.Timestamp, ts.UnixNano()) + } + + // Reset and test the other code path + a.fragment = nil + a.AddTokenToFragment(namespace_id) + + if a.fragment == nil { + t.Fatal("no fragment created") + } + + if a.fragment.NonEntityTokens == nil { + t.Fatal("no fragment token map") + } + + actual := a.fragment.NonEntityTokens[namespace_id] + if actual != 1 { + t.Errorf("mismatched number of tokens, %v vs %v", actual, 1) + } + +} diff --git a/vault/core.go b/vault/core.go index 234cd7d1d..e02157a6a 100644 --- a/vault/core.go +++ b/vault/core.go @@ -346,6 +346,9 @@ type Core struct { // identityStore is used to manage client entities identityStore *IdentityStore + // activityLog is used to track active client count + activityLog *ActivityLog + // metricsCh is used to stop the metrics streaming metricsCh chan struct{} @@ -1933,6 +1936,9 @@ func (s standardUnsealStrategy) unseal(ctx context.Context, logger log.Logger, c if err := c.setupAuditedHeadersConfig(ctx); err != nil { return err } + if err := c.setupActivityLog(ctx); err != nil { + return err + } } else { c.auditBroker = NewAuditBroker(c.logger) }