diff --git a/api/logical.go b/api/logical.go index 1f208e3f2d..4f1d7e7789 100644 --- a/api/logical.go +++ b/api/logical.go @@ -300,6 +300,18 @@ func (c *Logical) addExtraHeaders(r *Request, headers http.Header) error { return nil } +func (c *Logical) PatchRaw(path string, data []byte) (*Response, error) { + return c.PatchRawWithContext(context.Background(), path, data) +} + +func (c *Logical) PatchRawWithContext(ctx context.Context, path string, data []byte) (*Response, error) { + r := c.c.NewRequest(http.MethodPatch, "/v1/"+path) + r.Headers.Set("Content-Type", "application/scim+json") + r.BodyBytes = data + + return c.writeRaw(ctx, r) +} + // Recover recovers the data at the given Vault path from a loaded snapshot. // The snapshotID parameter is the ID of the loaded snapshot func (c *Logical) Recover(ctx context.Context, path string, snapshotID string) (*Secret, error) { diff --git a/helper/identity/types.pb.go b/helper/identity/types.pb.go index 4d8ab5007b..0dc04ad1da 100644 --- a/helper/identity/types.pb.go +++ b/helper/identity/types.pb.go @@ -79,7 +79,13 @@ type Group struct { // belongs to. Do not return this value over the API when reading the // group. // @inject_tag: sentinel:"-" - NamespaceID string `protobuf:"bytes,13,opt,name=namespace_id,json=namespaceID,proto3" json:"namespace_id,omitempty" sentinel:"-"` + NamespaceID string `protobuf:"bytes,13,opt,name=namespace_id,json=namespaceID,proto3" json:"namespace_id,omitempty" sentinel:"-"` + // ScimClientID records the unique identifier of the SCIM client that + // originally created this Group. This establishes a strict ownership model, + // ensuring that authoritative lifecycle operations (like updates and deletes by + // an IGA) can only be performed by the client that owns the resource. + // @inject_tag: sentinel:"-" + ScimClientID string `protobuf:"bytes,14,opt,name=scim_client_id,json=scimClientId,proto3" json:"scim_client_id,omitempty" sentinel:"-"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -205,6 +211,13 @@ func (x *Group) GetNamespaceID() string { return "" } +func (x *Group) GetScimClientID() string { + if x != nil { + return x.ScimClientID + } + return "" +} + // LocalAliases holds the aliases belonging to an entity that are local to the // cluster. type LocalAliases struct { @@ -312,7 +325,20 @@ type Entity struct { // belongs to. Do not return this value over the API when reading the // entity. // @inject_tag: sentinel:"-" - NamespaceID string `protobuf:"bytes,12,opt,name=namespace_id,json=namespaceID,proto3" json:"namespace_id,omitempty" sentinel:"-"` + NamespaceID string `protobuf:"bytes,12,opt,name=namespace_id,json=namespaceID,proto3" json:"namespace_id,omitempty" sentinel:"-"` + // ExternalID stores the `externalId` provided by a SCIM client. + // This field serves as the canonical correlation key, linking this Vault Entity + // to its representation in the external identity management system. It is + // crucial for preventing the creation of duplicate entities when multiple + // SCIM clients provision the same user. + // @inject_tag: sentinel:"-" + ExternalID string `protobuf:"bytes,13,opt,name=external_id,json=externalId,proto3" json:"external_id,omitempty" sentinel:"-"` + // ScimClientID records the unique identifier of the SCIM client that + // originally created this Entity. This establishes a strict ownership model, + // ensuring that authoritative lifecycle operations (like updates and deletes by + // an IGA) can only be performed by the client that owns the resource. + // @inject_tag: sentinel:"-" + ScimClientID string `protobuf:"bytes,14,opt,name=scim_client_id,json=scimClientId,proto3" json:"scim_client_id,omitempty" sentinel:"-"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -431,6 +457,20 @@ func (x *Entity) GetNamespaceID() string { return "" } +func (x *Entity) GetExternalID() string { + if x != nil { + return x.ExternalID + } + return "" +} + +func (x *Entity) GetScimClientID() string { + if x != nil { + return x.ScimClientID + } + return "" +} + // Alias represents the alias that gets stored inside of the // entity object in storage and also represents in an in-memory index of an // alias object. @@ -495,8 +535,14 @@ type Alias struct { // during invalidation of local aliases in performance standbys. // @inject_tag: sentinel:"-" LocalBucketKey string `protobuf:"bytes,14,opt,name=local_bucket_key,json=localBucketKey,proto3" json:"local_bucket_key,omitempty" sentinel:"-"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // ScimClientID records the unique identifier of the SCIM client that + // originally created this Alias. This establishes a strict ownership model, + // ensuring that authoritative lifecycle operations (like updates and deletes by + // an IGA) can only be performed by the client that owns the resource. + // @inject_tag: sentinel:"-" + ScimClientID string `protobuf:"bytes,15,opt,name=scim_client_id,json=scimClientId,proto3" json:"scim_client_id,omitempty" sentinel:"-"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *Alias) Reset() { @@ -627,6 +673,94 @@ func (x *Alias) GetLocalBucketKey() string { return "" } +func (x *Alias) GetScimClientID() string { + if x != nil { + return x.ScimClientID + } + return "" +} + +// ScimConfig defines the stored configuration for a single SCIM client. +// This configuration links a client's identity within Vault to its specific +// role and capabilities within the SCIM server. +type ScimConfig struct { + state protoimpl.MessageState `protogen:"open.v1"` + // ClientID is a unique, user-defined identifier for this specific SCIM + // client configuration (e.g., 'Okta-Prod', 'SailPoint-Dev'). + ClientID string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` + // ClientRole defines the client's function and authoritative power. + // It must be either "IGA" (authoritative) or "IDP" (standard). + ClientRole string `protobuf:"bytes,2,opt,name=client_role,json=clientRole,proto3" json:"client_role,omitempty"` + // AccessGrantPrincipal is the Vault Entity ID that represents the SCIM + // client application itself. This is the principal that will be granted the + // necessary permissions to perform SCIM operations. + AccessGrantPrincipal string `protobuf:"bytes,3,opt,name=access_grant_principal,json=accessGrantPrincipal,proto3" json:"access_grant_principal,omitempty"` + // AliasMountAccessor is an optional field that specifies the mount accessor + // of an auth method where login aliases should be created for provisioned users. + // This is typically used for clients with the 'IDP' role. + AliasMountAccessor string `protobuf:"bytes,4,opt,name=alias_mount_accessor,json=aliasMountAccessor,proto3" json:"alias_mount_accessor,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ScimConfig) Reset() { + *x = ScimConfig{} + mi := &file_helper_identity_types_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ScimConfig) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ScimConfig) ProtoMessage() {} + +func (x *ScimConfig) ProtoReflect() protoreflect.Message { + mi := &file_helper_identity_types_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ScimConfig.ProtoReflect.Descriptor instead. +func (*ScimConfig) Descriptor() ([]byte, []int) { + return file_helper_identity_types_proto_rawDescGZIP(), []int{4} +} + +func (x *ScimConfig) GetClientID() string { + if x != nil { + return x.ClientID + } + return "" +} + +func (x *ScimConfig) GetClientRole() string { + if x != nil { + return x.ClientRole + } + return "" +} + +func (x *ScimConfig) GetAccessGrantPrincipal() string { + if x != nil { + return x.AccessGrantPrincipal + } + return "" +} + +func (x *ScimConfig) GetAliasMountAccessor() string { + if x != nil { + return x.AliasMountAccessor + } + return "" +} + // Deprecated. Retained for backwards compatibility. type EntityStorageEntry struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -646,7 +780,7 @@ type EntityStorageEntry struct { func (x *EntityStorageEntry) Reset() { *x = EntityStorageEntry{} - mi := &file_helper_identity_types_proto_msgTypes[4] + mi := &file_helper_identity_types_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -658,7 +792,7 @@ func (x *EntityStorageEntry) String() string { func (*EntityStorageEntry) ProtoMessage() {} func (x *EntityStorageEntry) ProtoReflect() protoreflect.Message { - mi := &file_helper_identity_types_proto_msgTypes[4] + mi := &file_helper_identity_types_proto_msgTypes[5] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -671,7 +805,7 @@ func (x *EntityStorageEntry) ProtoReflect() protoreflect.Message { // Deprecated: Use EntityStorageEntry.ProtoReflect.Descriptor instead. func (*EntityStorageEntry) Descriptor() ([]byte, []int) { - return file_helper_identity_types_proto_rawDescGZIP(), []int{4} + return file_helper_identity_types_proto_rawDescGZIP(), []int{5} } func (x *EntityStorageEntry) GetPersonas() []*PersonaIndexEntry { @@ -763,7 +897,7 @@ type PersonaIndexEntry struct { func (x *PersonaIndexEntry) Reset() { *x = PersonaIndexEntry{} - mi := &file_helper_identity_types_proto_msgTypes[5] + mi := &file_helper_identity_types_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -775,7 +909,7 @@ func (x *PersonaIndexEntry) String() string { func (*PersonaIndexEntry) ProtoMessage() {} func (x *PersonaIndexEntry) ProtoReflect() protoreflect.Message { - mi := &file_helper_identity_types_proto_msgTypes[5] + mi := &file_helper_identity_types_proto_msgTypes[6] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -788,7 +922,7 @@ func (x *PersonaIndexEntry) ProtoReflect() protoreflect.Message { // Deprecated: Use PersonaIndexEntry.ProtoReflect.Descriptor instead. func (*PersonaIndexEntry) Descriptor() ([]byte, []int) { - return file_helper_identity_types_proto_rawDescGZIP(), []int{5} + return file_helper_identity_types_proto_rawDescGZIP(), []int{6} } func (x *PersonaIndexEntry) GetID() string { @@ -870,7 +1004,7 @@ var file_helper_identity_types_proto_rawDesc = string([]byte{ 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x68, 0x65, 0x6c, 0x70, 0x65, 0x72, 0x2f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2f, 0x6d, 0x66, 0x61, 0x2f, 0x74, 0x79, - 0x70, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xbc, 0x04, 0x0a, 0x05, 0x47, 0x72, + 0x70, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xe2, 0x04, 0x0a, 0x05, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, @@ -902,178 +1036,198 @@ var file_helper_identity_types_proto_rawDesc = string([]byte{ 0x6c, 0x69, 0x61, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, - 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x49, 0x64, 0x1a, 0x3b, 0x0a, 0x0d, 0x4d, - 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 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, 0x09, 0x52, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x39, 0x0a, 0x0c, 0x4c, 0x6f, 0x63, 0x61, - 0x6c, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x12, 0x29, 0x0a, 0x07, 0x61, 0x6c, 0x69, 0x61, - 0x73, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x69, 0x64, 0x65, 0x6e, - 0x74, 0x69, 0x74, 0x79, 0x2e, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x52, 0x07, 0x61, 0x6c, 0x69, 0x61, - 0x73, 0x65, 0x73, 0x22, 0x8c, 0x05, 0x0a, 0x06, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x29, + 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x73, + 0x63, 0x69, 0x6d, 0x5f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x0e, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0c, 0x73, 0x63, 0x69, 0x6d, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, + 0x64, 0x1a, 0x3b, 0x0a, 0x0d, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 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, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x39, + 0x0a, 0x0c, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x12, 0x29, 0x0a, 0x07, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x41, 0x6c, 0x69, 0x61, 0x73, - 0x52, 0x07, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, - 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x3a, 0x0a, - 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x1e, 0x2e, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, - 0x79, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, - 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x3f, 0x0a, 0x0d, 0x63, 0x72, 0x65, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0c, 0x63, 0x72, - 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x44, 0x0a, 0x10, 0x6c, 0x61, - 0x73, 0x74, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x06, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, - 0x52, 0x0e, 0x6c, 0x61, 0x73, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, - 0x12, 0x2a, 0x0a, 0x11, 0x6d, 0x65, 0x72, 0x67, 0x65, 0x64, 0x5f, 0x65, 0x6e, 0x74, 0x69, 0x74, - 0x79, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x6d, 0x65, 0x72, - 0x67, 0x65, 0x64, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x49, 0x64, 0x73, 0x12, 0x1a, 0x0a, 0x08, - 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, - 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x75, 0x63, 0x6b, - 0x65, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x62, 0x75, - 0x63, 0x6b, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x41, 0x0a, 0x0b, 0x6d, 0x66, 0x61, 0x5f, 0x73, - 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x69, - 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x4d, - 0x66, 0x61, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, - 0x6d, 0x66, 0x61, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x69, - 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x64, 0x69, - 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, - 0x61, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6e, 0x61, - 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x49, 0x64, 0x1a, 0x3b, 0x0a, 0x0d, 0x4d, 0x65, 0x74, - 0x61, 0x64, 0x61, 0x74, 0x61, 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, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x4a, 0x0a, 0x0f, 0x4d, 0x66, 0x61, 0x53, 0x65, 0x63, - 0x72, 0x65, 0x74, 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, 0x21, 0x0a, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x6d, 0x66, 0x61, - 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, - 0x38, 0x01, 0x22, 0xe1, 0x05, 0x0a, 0x05, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x12, 0x0e, 0x0a, 0x02, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x21, 0x0a, 0x0c, - 0x63, 0x61, 0x6e, 0x6f, 0x6e, 0x69, 0x63, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0b, 0x63, 0x61, 0x6e, 0x6f, 0x6e, 0x69, 0x63, 0x61, 0x6c, 0x49, 0x64, 0x12, - 0x1d, 0x0a, 0x0a, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x09, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x25, - 0x0a, 0x0e, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x6f, 0x72, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x41, 0x63, 0x63, - 0x65, 0x73, 0x73, 0x6f, 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x70, - 0x61, 0x74, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6d, 0x6f, 0x75, 0x6e, 0x74, - 0x50, 0x61, 0x74, 0x68, 0x12, 0x39, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, - 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, - 0x79, 0x2e, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, + 0x52, 0x07, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x22, 0xd3, 0x05, 0x0a, 0x06, 0x45, 0x6e, + 0x74, 0x69, 0x74, 0x79, 0x12, 0x29, 0x0a, 0x07, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x18, + 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, + 0x2e, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x52, 0x07, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x12, + 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, + 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x12, 0x3a, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, + 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, + 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, - 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, - 0x61, 0x6d, 0x65, 0x12, 0x3f, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, - 0x74, 0x69, 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, + 0x3f, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x69, 0x6d, 0x65, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, + 0x6d, 0x70, 0x52, 0x0c, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x69, 0x6d, 0x65, + 0x12, 0x44, 0x0a, 0x10, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, + 0x74, 0x69, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, - 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0c, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x54, 0x69, 0x6d, 0x65, 0x12, 0x44, 0x0a, 0x10, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x75, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, - 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, - 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0e, 0x6c, 0x61, 0x73, 0x74, - 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x39, 0x0a, 0x19, 0x6d, 0x65, - 0x72, 0x67, 0x65, 0x64, 0x5f, 0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x63, 0x61, 0x6e, 0x6f, 0x6e, 0x69, - 0x63, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x09, 0x52, 0x16, 0x6d, - 0x65, 0x72, 0x67, 0x65, 0x64, 0x46, 0x72, 0x6f, 0x6d, 0x43, 0x61, 0x6e, 0x6f, 0x6e, 0x69, 0x63, - 0x61, 0x6c, 0x49, 0x64, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, - 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6e, 0x61, 0x6d, - 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x49, 0x64, 0x12, 0x4c, 0x0a, 0x0f, 0x63, 0x75, 0x73, 0x74, - 0x6f, 0x6d, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x0c, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x23, 0x2e, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x41, 0x6c, 0x69, - 0x61, 0x73, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, - 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0e, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, - 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x18, - 0x0d, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x12, 0x28, 0x0a, 0x10, - 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x62, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x5f, 0x6b, 0x65, 0x79, - 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x42, 0x75, 0x63, - 0x6b, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x1a, 0x3b, 0x0a, 0x0d, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, + 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0e, 0x6c, 0x61, 0x73, 0x74, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x6d, 0x65, 0x72, 0x67, 0x65, 0x64, + 0x5f, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, + 0x09, 0x52, 0x0f, 0x6d, 0x65, 0x72, 0x67, 0x65, 0x64, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x49, + 0x64, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x18, 0x08, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x12, 0x1d, + 0x0a, 0x0a, 0x62, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x09, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x09, 0x62, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x41, 0x0a, + 0x0b, 0x6d, 0x66, 0x61, 0x5f, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x18, 0x0a, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x45, 0x6e, + 0x74, 0x69, 0x74, 0x79, 0x2e, 0x4d, 0x66, 0x61, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, 0x6d, 0x66, 0x61, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, + 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x0b, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x08, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x21, 0x0a, 0x0c, + 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x0c, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0b, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x49, 0x64, 0x12, + 0x1f, 0x0a, 0x0b, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x0d, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x49, 0x64, + 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x63, 0x69, 0x6d, 0x5f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, + 0x69, 0x64, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x73, 0x63, 0x69, 0x6d, 0x43, 0x6c, + 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x1a, 0x3b, 0x0a, 0x0d, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 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, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, - 0x02, 0x38, 0x01, 0x1a, 0x41, 0x0a, 0x13, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, 0x74, + 0x02, 0x38, 0x01, 0x1a, 0x4a, 0x0a, 0x0f, 0x4d, 0x66, 0x61, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, + 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, 0x21, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x6d, 0x66, 0x61, 0x2e, 0x53, 0x65, + 0x63, 0x72, 0x65, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, + 0x87, 0x06, 0x0a, 0x05, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x61, 0x6e, + 0x6f, 0x6e, 0x69, 0x63, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0b, 0x63, 0x61, 0x6e, 0x6f, 0x6e, 0x69, 0x63, 0x61, 0x6c, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, + 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x6d, + 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x6f, 0x72, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, + 0x6f, 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x70, 0x61, 0x74, 0x68, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x50, 0x61, 0x74, + 0x68, 0x12, 0x39, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x06, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x41, + 0x6c, 0x69, 0x61, 0x73, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x12, 0x0a, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x12, 0x3f, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x69, 0x6d, + 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x52, 0x0c, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x69, 0x6d, + 0x65, 0x12, 0x44, 0x0a, 0x10, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, + 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0e, 0x6c, 0x61, 0x73, 0x74, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x39, 0x0a, 0x19, 0x6d, 0x65, 0x72, 0x67, 0x65, + 0x64, 0x5f, 0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x63, 0x61, 0x6e, 0x6f, 0x6e, 0x69, 0x63, 0x61, 0x6c, + 0x5f, 0x69, 0x64, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x09, 0x52, 0x16, 0x6d, 0x65, 0x72, 0x67, + 0x65, 0x64, 0x46, 0x72, 0x6f, 0x6d, 0x43, 0x61, 0x6e, 0x6f, 0x6e, 0x69, 0x63, 0x61, 0x6c, 0x49, + 0x64, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, + 0x69, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, + 0x61, 0x63, 0x65, 0x49, 0x64, 0x12, 0x4c, 0x0a, 0x0f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, + 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x23, + 0x2e, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x2e, + 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, + 0x74, 0x72, 0x79, 0x52, 0x0e, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, 0x74, 0x61, 0x64, + 0x61, 0x74, 0x61, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x18, 0x0d, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x05, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x12, 0x28, 0x0a, 0x10, 0x6c, 0x6f, 0x63, + 0x61, 0x6c, 0x5f, 0x62, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x0e, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, + 0x4b, 0x65, 0x79, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x63, 0x69, 0x6d, 0x5f, 0x63, 0x6c, 0x69, 0x65, + 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x73, 0x63, 0x69, + 0x6d, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x1a, 0x3b, 0x0a, 0x0d, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 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, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x88, 0x05, 0x0a, 0x12, 0x45, 0x6e, 0x74, 0x69, 0x74, - 0x79, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x37, 0x0a, - 0x08, 0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x61, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x1b, 0x2e, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x50, 0x65, 0x72, 0x73, 0x6f, - 0x6e, 0x61, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x70, 0x65, - 0x72, 0x73, 0x6f, 0x6e, 0x61, 0x73, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x46, 0x0a, 0x08, 0x6d, 0x65, - 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x69, - 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x53, 0x74, - 0x6f, 0x72, 0x61, 0x67, 0x65, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, - 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, - 0x74, 0x61, 0x12, 0x3f, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, - 0x69, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, - 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0c, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, - 0x69, 0x6d, 0x65, 0x12, 0x44, 0x0a, 0x10, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x75, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, - 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0e, 0x6c, 0x61, 0x73, 0x74, 0x55, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x6d, 0x65, 0x72, - 0x67, 0x65, 0x64, 0x5f, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x07, - 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x6d, 0x65, 0x72, 0x67, 0x65, 0x64, 0x45, 0x6e, 0x74, 0x69, - 0x74, 0x79, 0x49, 0x64, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, - 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, - 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x62, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x5f, - 0x68, 0x61, 0x73, 0x68, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x62, 0x75, 0x63, 0x6b, - 0x65, 0x74, 0x4b, 0x65, 0x79, 0x48, 0x61, 0x73, 0x68, 0x12, 0x4d, 0x0a, 0x0b, 0x6d, 0x66, 0x61, - 0x5f, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2c, - 0x2e, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, - 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x4d, 0x66, 0x61, - 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, 0x6d, 0x66, - 0x61, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x1a, 0x3b, 0x0a, 0x0d, 0x4d, 0x65, 0x74, 0x61, - 0x64, 0x61, 0x74, 0x61, 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, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x4a, 0x0a, 0x0f, 0x4d, 0x66, 0x61, 0x53, 0x65, 0x63, 0x72, - 0x65, 0x74, 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, 0x21, 0x0a, 0x05, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x6d, 0x66, 0x61, 0x2e, - 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, - 0x01, 0x22, 0xf9, 0x03, 0x0a, 0x11, 0x50, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x61, 0x49, 0x6e, 0x64, - 0x65, 0x78, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x65, 0x6e, 0x74, 0x69, 0x74, - 0x79, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x6e, 0x74, 0x69, - 0x74, 0x79, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x74, 0x79, - 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x54, - 0x79, 0x70, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x61, 0x63, 0x63, - 0x65, 0x73, 0x73, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6d, 0x6f, 0x75, - 0x6e, 0x74, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x6f, 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x6f, - 0x75, 0x6e, 0x74, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, - 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x50, 0x61, 0x74, 0x68, 0x12, 0x45, 0x0a, 0x08, 0x6d, 0x65, 0x74, - 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x69, 0x64, - 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x50, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x61, 0x49, 0x6e, - 0x64, 0x65, 0x78, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, - 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, - 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, - 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x3f, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, - 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0c, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x44, 0x0a, 0x10, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x75, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0e, 0x6c, 0x61, 0x73, - 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x33, 0x0a, 0x16, 0x6d, - 0x65, 0x72, 0x67, 0x65, 0x64, 0x5f, 0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x65, 0x6e, 0x74, 0x69, 0x74, - 0x79, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x09, 0x52, 0x13, 0x6d, 0x65, 0x72, - 0x67, 0x65, 0x64, 0x46, 0x72, 0x6f, 0x6d, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x49, 0x64, 0x73, + 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x41, 0x0a, 0x13, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, + 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 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, 0x09, 0x52, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xb2, 0x01, 0x0a, 0x0a, 0x53, 0x63, + 0x69, 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, + 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6c, 0x69, + 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, + 0x72, 0x6f, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6c, 0x69, 0x65, + 0x6e, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x12, 0x34, 0x0a, 0x16, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, + 0x5f, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x14, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x47, 0x72, + 0x61, 0x6e, 0x74, 0x50, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x12, 0x30, 0x0a, 0x14, + 0x61, 0x6c, 0x69, 0x61, 0x73, 0x5f, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x61, 0x63, 0x63, 0x65, + 0x73, 0x73, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x61, 0x6c, 0x69, 0x61, + 0x73, 0x4d, 0x6f, 0x75, 0x6e, 0x74, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x6f, 0x72, 0x22, 0x88, + 0x05, 0x0a, 0x12, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x37, 0x0a, 0x08, 0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x61, + 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, + 0x74, 0x79, 0x2e, 0x50, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x61, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x61, 0x73, 0x12, 0x0e, + 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, + 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x12, 0x46, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, + 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, + 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x3f, 0x0a, 0x0d, 0x63, 0x72, + 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0c, 0x63, + 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x44, 0x0a, 0x10, 0x6c, + 0x61, 0x73, 0x74, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, + 0x70, 0x52, 0x0e, 0x6c, 0x61, 0x73, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, + 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x6d, 0x65, 0x72, 0x67, 0x65, 0x64, 0x5f, 0x65, 0x6e, 0x74, 0x69, + 0x74, 0x79, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x6d, 0x65, + 0x72, 0x67, 0x65, 0x64, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x49, 0x64, 0x73, 0x12, 0x1a, 0x0a, + 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, + 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x62, 0x75, 0x63, + 0x6b, 0x65, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x09, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0d, 0x62, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x48, 0x61, 0x73, + 0x68, 0x12, 0x4d, 0x0a, 0x0b, 0x6d, 0x66, 0x61, 0x5f, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, + 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, + 0x79, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x4d, 0x66, 0x61, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, 0x6d, 0x66, 0x61, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x1a, 0x3b, 0x0a, 0x0d, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 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, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x2c, 0x5a, - 0x2a, 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, 0x68, 0x65, 0x6c, 0x70, - 0x65, 0x72, 0x2f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x62, 0x06, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x33, + 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x4a, 0x0a, + 0x0f, 0x4d, 0x66, 0x61, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 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, 0x21, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x0b, 0x2e, 0x6d, 0x66, 0x61, 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xf9, 0x03, 0x0a, 0x11, 0x50, 0x65, + 0x72, 0x73, 0x6f, 0x6e, 0x61, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, + 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, + 0x1b, 0x0a, 0x09, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x08, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, + 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x6d, + 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x6f, 0x72, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, + 0x6f, 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x70, 0x61, 0x74, 0x68, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x50, 0x61, 0x74, + 0x68, 0x12, 0x45, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x06, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x50, + 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x61, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x45, 0x6e, 0x74, 0x72, 0x79, + 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, + 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x3f, 0x0a, 0x0d, + 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x08, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, + 0x0c, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x44, 0x0a, + 0x10, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, + 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x52, 0x0e, 0x6c, 0x61, 0x73, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, + 0x69, 0x6d, 0x65, 0x12, 0x33, 0x0a, 0x16, 0x6d, 0x65, 0x72, 0x67, 0x65, 0x64, 0x5f, 0x66, 0x72, + 0x6f, 0x6d, 0x5f, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x0a, 0x20, + 0x03, 0x28, 0x09, 0x52, 0x13, 0x6d, 0x65, 0x72, 0x67, 0x65, 0x64, 0x46, 0x72, 0x6f, 0x6d, 0x45, + 0x6e, 0x74, 0x69, 0x74, 0x79, 0x49, 0x64, 0x73, 0x1a, 0x3b, 0x0a, 0x0d, 0x4d, 0x65, 0x74, 0x61, + 0x64, 0x61, 0x74, 0x61, 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, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x2c, 0x5a, 0x2a, 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, 0x68, 0x65, 0x6c, 0x70, 0x65, 0x72, 0x2f, 0x69, 0x64, 0x65, 0x6e, 0x74, + 0x69, 0x74, 0x79, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, }) var ( @@ -1088,50 +1242,51 @@ func file_helper_identity_types_proto_rawDescGZIP() []byte { return file_helper_identity_types_proto_rawDescData } -var file_helper_identity_types_proto_msgTypes = make([]protoimpl.MessageInfo, 14) +var file_helper_identity_types_proto_msgTypes = make([]protoimpl.MessageInfo, 15) var file_helper_identity_types_proto_goTypes = []any{ (*Group)(nil), // 0: identity.Group (*LocalAliases)(nil), // 1: identity.LocalAliases (*Entity)(nil), // 2: identity.Entity (*Alias)(nil), // 3: identity.Alias - (*EntityStorageEntry)(nil), // 4: identity.EntityStorageEntry - (*PersonaIndexEntry)(nil), // 5: identity.PersonaIndexEntry - nil, // 6: identity.Group.MetadataEntry - nil, // 7: identity.Entity.MetadataEntry - nil, // 8: identity.Entity.MFASecretsEntry - nil, // 9: identity.Alias.MetadataEntry - nil, // 10: identity.Alias.CustomMetadataEntry - nil, // 11: identity.EntityStorageEntry.MetadataEntry - nil, // 12: identity.EntityStorageEntry.MFASecretsEntry - nil, // 13: identity.PersonaIndexEntry.MetadataEntry - (*timestamppb.Timestamp)(nil), // 14: google.protobuf.Timestamp - (*mfa.Secret)(nil), // 15: mfa.Secret + (*ScimConfig)(nil), // 4: identity.ScimConfig + (*EntityStorageEntry)(nil), // 5: identity.EntityStorageEntry + (*PersonaIndexEntry)(nil), // 6: identity.PersonaIndexEntry + nil, // 7: identity.Group.MetadataEntry + nil, // 8: identity.Entity.MetadataEntry + nil, // 9: identity.Entity.MFASecretsEntry + nil, // 10: identity.Alias.MetadataEntry + nil, // 11: identity.Alias.CustomMetadataEntry + nil, // 12: identity.EntityStorageEntry.MetadataEntry + nil, // 13: identity.EntityStorageEntry.MFASecretsEntry + nil, // 14: identity.PersonaIndexEntry.MetadataEntry + (*timestamppb.Timestamp)(nil), // 15: google.protobuf.Timestamp + (*mfa.Secret)(nil), // 16: mfa.Secret } var file_helper_identity_types_proto_depIDxs = []int32{ - 6, // 0: identity.Group.metadata:type_name -> identity.Group.MetadataEntry - 14, // 1: identity.Group.creation_time:type_name -> google.protobuf.Timestamp - 14, // 2: identity.Group.last_update_time:type_name -> google.protobuf.Timestamp + 7, // 0: identity.Group.metadata:type_name -> identity.Group.MetadataEntry + 15, // 1: identity.Group.creation_time:type_name -> google.protobuf.Timestamp + 15, // 2: identity.Group.last_update_time:type_name -> google.protobuf.Timestamp 3, // 3: identity.Group.alias:type_name -> identity.Alias 3, // 4: identity.LocalAliases.aliases:type_name -> identity.Alias 3, // 5: identity.Entity.aliases:type_name -> identity.Alias - 7, // 6: identity.Entity.metadata:type_name -> identity.Entity.MetadataEntry - 14, // 7: identity.Entity.creation_time:type_name -> google.protobuf.Timestamp - 14, // 8: identity.Entity.last_update_time:type_name -> google.protobuf.Timestamp - 8, // 9: identity.Entity.mfa_secrets:type_name -> identity.Entity.MFASecretsEntry - 9, // 10: identity.Alias.metadata:type_name -> identity.Alias.MetadataEntry - 14, // 11: identity.Alias.creation_time:type_name -> google.protobuf.Timestamp - 14, // 12: identity.Alias.last_update_time:type_name -> google.protobuf.Timestamp - 10, // 13: identity.Alias.custom_metadata:type_name -> identity.Alias.CustomMetadataEntry - 5, // 14: identity.EntityStorageEntry.personas:type_name -> identity.PersonaIndexEntry - 11, // 15: identity.EntityStorageEntry.metadata:type_name -> identity.EntityStorageEntry.MetadataEntry - 14, // 16: identity.EntityStorageEntry.creation_time:type_name -> google.protobuf.Timestamp - 14, // 17: identity.EntityStorageEntry.last_update_time:type_name -> google.protobuf.Timestamp - 12, // 18: identity.EntityStorageEntry.mfa_secrets:type_name -> identity.EntityStorageEntry.MFASecretsEntry - 13, // 19: identity.PersonaIndexEntry.metadata:type_name -> identity.PersonaIndexEntry.MetadataEntry - 14, // 20: identity.PersonaIndexEntry.creation_time:type_name -> google.protobuf.Timestamp - 14, // 21: identity.PersonaIndexEntry.last_update_time:type_name -> google.protobuf.Timestamp - 15, // 22: identity.Entity.MFASecretsEntry.value:type_name -> mfa.Secret - 15, // 23: identity.EntityStorageEntry.MFASecretsEntry.value:type_name -> mfa.Secret + 8, // 6: identity.Entity.metadata:type_name -> identity.Entity.MetadataEntry + 15, // 7: identity.Entity.creation_time:type_name -> google.protobuf.Timestamp + 15, // 8: identity.Entity.last_update_time:type_name -> google.protobuf.Timestamp + 9, // 9: identity.Entity.mfa_secrets:type_name -> identity.Entity.MFASecretsEntry + 10, // 10: identity.Alias.metadata:type_name -> identity.Alias.MetadataEntry + 15, // 11: identity.Alias.creation_time:type_name -> google.protobuf.Timestamp + 15, // 12: identity.Alias.last_update_time:type_name -> google.protobuf.Timestamp + 11, // 13: identity.Alias.custom_metadata:type_name -> identity.Alias.CustomMetadataEntry + 6, // 14: identity.EntityStorageEntry.personas:type_name -> identity.PersonaIndexEntry + 12, // 15: identity.EntityStorageEntry.metadata:type_name -> identity.EntityStorageEntry.MetadataEntry + 15, // 16: identity.EntityStorageEntry.creation_time:type_name -> google.protobuf.Timestamp + 15, // 17: identity.EntityStorageEntry.last_update_time:type_name -> google.protobuf.Timestamp + 13, // 18: identity.EntityStorageEntry.mfa_secrets:type_name -> identity.EntityStorageEntry.MFASecretsEntry + 14, // 19: identity.PersonaIndexEntry.metadata:type_name -> identity.PersonaIndexEntry.MetadataEntry + 15, // 20: identity.PersonaIndexEntry.creation_time:type_name -> google.protobuf.Timestamp + 15, // 21: identity.PersonaIndexEntry.last_update_time:type_name -> google.protobuf.Timestamp + 16, // 22: identity.Entity.MFASecretsEntry.value:type_name -> mfa.Secret + 16, // 23: identity.EntityStorageEntry.MFASecretsEntry.value:type_name -> mfa.Secret 24, // [24:24] is the sub-list for method output_type 24, // [24:24] is the sub-list for method input_type 24, // [24:24] is the sub-list for extension type_name @@ -1150,7 +1305,7 @@ func file_helper_identity_types_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_helper_identity_types_proto_rawDesc), len(file_helper_identity_types_proto_rawDesc)), NumEnums: 0, - NumMessages: 14, + NumMessages: 15, NumExtensions: 0, NumServices: 0, }, diff --git a/helper/identity/types.proto b/helper/identity/types.proto index b5759d012f..e474cdea4e 100644 --- a/helper/identity/types.proto +++ b/helper/identity/types.proto @@ -75,6 +75,13 @@ message Group { // group. // @inject_tag: sentinel:"-" string namespace_id = 13; + + // ScimClientId records the unique identifier of the SCIM client that + // originally created this Group. This establishes a strict ownership model, + // ensuring that authoritative lifecycle operations (like updates and deletes by + // an IGA) can only be performed by the client that owns the resource. + // @inject_tag: sentinel:"-" + string scim_client_id = 14; } // LocalAliases holds the aliases belonging to an entity that are local to the @@ -155,6 +162,21 @@ message Entity { // entity. // @inject_tag: sentinel:"-" string namespace_id = 12; + + // ExternalId stores the `externalId` provided by a SCIM client. + // This field serves as the canonical correlation key, linking this Vault Entity + // to its representation in the external identity management system. It is + // crucial for preventing the creation of duplicate entities when multiple + // SCIM clients provision the same user. + // @inject_tag: sentinel:"-" + string external_id = 13; + + // ScimClientId records the unique identifier of the SCIM client that + // originally created this Entity. This establishes a strict ownership model, + // ensuring that authoritative lifecycle operations (like updates and deletes by + // an IGA) can only be performed by the client that owns the resource. + // @inject_tag: sentinel:"-" + string scim_client_id = 14; } // Alias represents the alias that gets stored inside of the @@ -233,6 +255,36 @@ message Alias { // during invalidation of local aliases in performance standbys. // @inject_tag: sentinel:"-" string local_bucket_key = 14; + + // ScimClientId records the unique identifier of the SCIM client that + // originally created this Alias. This establishes a strict ownership model, + // ensuring that authoritative lifecycle operations (like updates and deletes by + // an IGA) can only be performed by the client that owns the resource. + // @inject_tag: sentinel:"-" + string scim_client_id = 15; +} + +// ScimConfig defines the stored configuration for a single SCIM client. +// This configuration links a client's identity within Vault to its specific +// role and capabilities within the SCIM server. +message ScimConfig { + // ClientId is a unique, user-defined identifier for this specific SCIM + // client configuration (e.g., 'Okta-Prod', 'SailPoint-Dev'). + string client_id = 1; + + // ClientRole defines the client's function and authoritative power. + // It must be either "IGA" (authoritative) or "IdP" (standard). + string client_role = 2; + + // AccessGrantPrincipal is the Vault Entity ID that represents the SCIM + // client application itself. This is the principal that will be granted the + // necessary permissions to perform SCIM operations. + string access_grant_principal = 3; + + // AliasMountAccessor is an optional field that specifies the mount accessor + // of an auth method where login aliases should be created for provisioned users. + // This is typically used for clients with the 'IdP' role. + string alias_mount_accessor = 4; } // Deprecated. Retained for backwards compatibility. diff --git a/http/logical.go b/http/logical.go index e1c6a7b737..00160d3eac 100644 --- a/http/logical.go +++ b/http/logical.go @@ -43,7 +43,10 @@ func (b *bufferedReader) Close() error { return b.rOrig.Close() } -const MergePatchContentTypeHeader = "application/merge-patch+json" +const ( + MergePatchContentTypeHeader = "application/merge-patch+json" + ScimPatchContentTypeHeader = "application/scim+json" +) func buildLogicalRequestNoAuth(perfStandby bool, ra *vault.RouterAccess, w http.ResponseWriter, r *http.Request) (*logical.Request, io.ReadCloser, int, error) { ns, err := namespace.FromContext(r.Context()) @@ -167,7 +170,7 @@ func buildLogicalRequestNoAuth(perfStandby bool, ra *vault.RouterAccess, w http. return nil, nil, status, err } - if contentType != MergePatchContentTypeHeader { + if contentType != MergePatchContentTypeHeader && contentType != ScimPatchContentTypeHeader { return nil, nil, http.StatusUnsupportedMediaType, fmt.Errorf("PATCH requires Content-Type of %s, provided %s", MergePatchContentTypeHeader, contentType) } diff --git a/vault/identity_store.go b/vault/identity_store.go index dff0e7f937..824461a097 100644 --- a/vault/identity_store.go +++ b/vault/identity_store.go @@ -33,6 +33,7 @@ import ( const ( groupBucketsPrefix = "packer/group/buckets/" localAliasesBucketsPrefix = "packer/local-aliases/buckets/" + scimBucketsPrefix = "packer/scim/buckets/" ) var ( @@ -95,6 +96,8 @@ func NewIdentityStore(ctx context.Context, core *Core, config *logical.BackendCo core.AddLogger(localAliasesPackerLogger) groupsPackerLogger := iStore.logger.Named("storagepacker").Named("groups") core.AddLogger(groupsPackerLogger) + scimPackerLogger := iStore.logger.Named("storagepacker").Named("scim") + core.AddLogger(scimPackerLogger) iStore.entityPacker, err = storagepacker.NewStoragePacker(iStore.view, entitiesPackerLogger, "") if err != nil { @@ -111,6 +114,11 @@ func NewIdentityStore(ctx context.Context, core *Core, config *logical.BackendCo return nil, fmt.Errorf("failed to create group packer: %w", err) } + iStore.scimConfigPacker, err = storagepacker.NewStoragePacker(iStore.view, scimPackerLogger, scimBucketsPrefix) + if err != nil { + return nil, fmt.Errorf("failed to create scim packer: %w", err) + } + iStore.Backend = &framework.Backend{ BackendType: logical.TypeLogical, Paths: iStore.paths(), @@ -123,7 +131,7 @@ func NewIdentityStore(ctx context.Context, core *Core, config *logical.BackendCo "oidc/+/.well-known/*", "oidc/provider/+/.well-known/*", "oidc/provider/+/token", - }, identityStoreLoginMFAEntUnauthedPaths()...), + }), LocalStorage: []string{ localAliasesBucketsPrefix, }, @@ -165,7 +173,6 @@ func (i *IdentityStore) paths() []*framework.Path { mfaDuoPaths(i), mfaPingIDPaths(i), mfaLoginEnforcementPaths(i), - mfaLoginEnterprisePaths(i), ) } diff --git a/vault/identity_store_aliases.go b/vault/identity_store_aliases.go index c668885370..4bce45ac50 100644 --- a/vault/identity_store_aliases.go +++ b/vault/identity_store_aliases.go @@ -35,34 +35,7 @@ func aliasPaths(i *IdentityStore) []*framework.Path { OperationSuffix: "alias", }, - Fields: map[string]*framework.FieldSchema{ - "id": { - Type: framework.TypeString, - Description: "ID of the entity alias. If set, updates the corresponding entity alias.", - }, - // entity_id is deprecated in favor of canonical_id - "entity_id": { - Type: framework.TypeString, - Description: `Entity ID to which this alias belongs. -This field is deprecated, use canonical_id.`, - }, - "canonical_id": { - Type: framework.TypeString, - Description: "Entity ID to which this alias belongs", - }, - "mount_accessor": { - Type: framework.TypeString, - Description: "Mount accessor to which this alias belongs to; unused for a modify", - }, - "name": { - Type: framework.TypeString, - Description: "Name of the alias; unused for a modify", - }, - "custom_metadata": { - Type: framework.TypeKVPairs, - Description: "User provided key-value pairs", - }, - }, + Fields: aliasFieldSchema(), Callbacks: map[logical.Operation]framework.OperationFunc{ logical.UpdateOperation: i.handleAliasCreateUpdate(), }, @@ -150,6 +123,37 @@ This field is deprecated, use canonical_id.`, } } +func aliasFieldSchema() map[string]*framework.FieldSchema { + return map[string]*framework.FieldSchema{ + "id": { + Type: framework.TypeString, + Description: "ID of the entity alias. If set, updates the corresponding entity alias.", + }, + // entity_id is deprecated in favor of canonical_id + "entity_id": { + Type: framework.TypeString, + Description: `Entity ID to which this alias belongs. +This field is deprecated, use canonical_id.`, + }, + "canonical_id": { + Type: framework.TypeString, + Description: "Entity ID to which this alias belongs", + }, + "mount_accessor": { + Type: framework.TypeString, + Description: "Mount accessor to which this alias belongs to; unused for a modify", + }, + "name": { + Type: framework.TypeString, + Description: "Name of the alias; unused for a modify", + }, + "custom_metadata": { + Type: framework.TypeKVPairs, + Description: "User provided key-value pairs", + }, + } +} + // handleAliasCreateUpdate is used to create or update an alias func (i *IdentityStore) handleAliasCreateUpdate() framework.OperationFunc { return func(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { @@ -239,7 +243,6 @@ func (i *IdentityStore) handleAliasCreateUpdate() framework.OperationFunc { return logical.ErrorResponse("'id' or 'mount_accessor' and 'name' must be provided"), nil } - // Look up the alias by factors; if it's found it's an update mountEntry := i.router.MatchingMountByAccessor(mountAccessor) if mountEntry == nil { return logical.ErrorResponse(fmt.Sprintf("invalid mount accessor %q", mountAccessor)), nil @@ -247,22 +250,30 @@ func (i *IdentityStore) handleAliasCreateUpdate() framework.OperationFunc { if mountEntry.NamespaceID != ns.ID { return logical.ErrorResponse("matching mount is in a different namespace than request"), logical.ErrPermissionDenied } - alias, err := i.MemDBAliasByFactors(mountAccessor, name, true, false) - if err != nil { - return nil, err - } - if alias != nil { - if alias.NamespaceID != ns.ID { - return logical.ErrorResponse("cannot modify aliases across namespaces"), logical.ErrPermissionDenied - } - return i.handleAliasUpdate(ctx, canonicalID, name, mountAccessor, alias, customMetadata) - } - // At this point we know it's a new creation request - return i.handleAliasCreate(ctx, canonicalID, name, mountAccessor, mountEntry.Local, customMetadata) + + localMount := mountEntry.Local + + // Look up the alias by factors; if it's found it's an update + return i.handleAliasCreateUpdateCommon(ctx, ns, mountAccessor, name, canonicalID, customMetadata, localMount, "") } } -func (i *IdentityStore) handleAliasCreate(ctx context.Context, canonicalID, name, mountAccessor string, local bool, customMetadata map[string]string) (*logical.Response, error) { +func (i *IdentityStore) handleAliasCreateUpdateCommon(ctx context.Context, ns *namespace.Namespace, mountAccessor string, name string, canonicalID string, customMetadata map[string]string, localMount bool, scimClientID string) (*logical.Response, error) { + alias, err := i.MemDBAliasByFactors(mountAccessor, name, true, false) + if err != nil { + return nil, err + } + if alias != nil { + if alias.NamespaceID != ns.ID { + return logical.ErrorResponse("cannot modify aliases across namespaces"), logical.ErrPermissionDenied + } + return i.handleAliasUpdate(ctx, canonicalID, name, mountAccessor, alias, customMetadata) + } + // At this point we know it's a new creation request + return i.handleAliasCreate(ctx, canonicalID, name, mountAccessor, localMount, customMetadata, scimClientID) +} + +func (i *IdentityStore) handleAliasCreate(ctx context.Context, canonicalID, name, mountAccessor string, local bool, customMetadata map[string]string, scimClientID string) (*logical.Response, error) { ns, err := namespace.FromContext(ctx) if err != nil { return nil, err @@ -326,6 +337,7 @@ func (i *IdentityStore) handleAliasCreate(ctx context.Context, canonicalID, name Name: name, CustomMetadata: customMetadata, CanonicalID: entity.ID, + ScimClientID: scimClientID, } err = i.sanitizeAlias(ctx, alias) if err != nil { diff --git a/vault/identity_store_entities.go b/vault/identity_store_entities.go index 8dc6940c26..b95e909c7f 100644 --- a/vault/identity_store_entities.go +++ b/vault/identity_store_entities.go @@ -31,6 +31,14 @@ func entityPathFields() map[string]*framework.FieldSchema { Type: framework.TypeString, Description: "Name of the entity", }, + "external_id": { + Type: framework.TypeString, + Description: "External ID of the entity", + }, + "scim_client_id": { + Type: framework.TypeString, + Description: "SCIM Client ID of the entity", + }, "metadata": { Type: framework.TypeKVPairs, Description: `Metadata to be associated with the entity. @@ -304,106 +312,7 @@ func (i *IdentityStore) handleEntityUpdateCommon() framework.OperationFunc { i.lock.Lock() defer i.lock.Unlock() - entity := new(identity.Entity) - var err error - - entityID := d.Get("id").(string) - if entityID != "" { - entity, err = i.MemDBEntityByID(entityID, true) - if err != nil { - return nil, err - } - if entity == nil { - return logical.ErrorResponse("entity not found from id"), nil - } - } - - // Get the name - entityName := d.Get("name").(string) - if entityName != "" { - entityByName, err := i.MemDBEntityByName(ctx, entityName, true) - if err != nil { - return nil, err - } - switch { - case entityByName == nil: - // Not found, safe to use this name with an existing or new entity - case entity.ID == "": - // Entity by ID was not found, but and entity for the supplied - // name was found. Continue updating the entity. - entity = entityByName - case entity.ID == entityByName.ID: - // Same exact entity, carry on (this is basically a noop then) - default: - return logical.ErrorResponse("entity name is already in use"), nil - } - } - - if entityName != "" { - entity.Name = entityName - } - - // Update the policies if supplied - entityPoliciesRaw, ok := d.GetOk("policies") - if ok { - entity.Policies = strutil.RemoveDuplicates(entityPoliciesRaw.([]string), false) - } - - if strutil.StrListContainsCaseInsensitive(entity.Policies, "root") { - return logical.ErrorResponse("policies cannot contain root"), nil - } - - disabledRaw, ok := d.GetOk("disabled") - if ok { - entity.Disabled = disabledRaw.(bool) - } - - // Get entity metadata - metadata, ok, err := d.GetOkErr("metadata") - if err != nil { - return logical.ErrorResponse(fmt.Sprintf("failed to parse metadata: %v", err)), nil - } - if ok { - entity.Metadata = metadata.(map[string]string) - } - - // At this point, if entity.ID is empty, it indicates that a new entity - // is being created. Using this to respond data in the response. - newEntity := entity.ID == "" - - // ID creation and some validations - err = i.sanitizeEntity(ctx, entity) - if err != nil { - return nil, err - } - - if err := i.upsertEntity(ctx, entity, nil, true); err != nil { - return nil, err - } - - // If this operation was an update to an existing entity, return 204 - if !newEntity { - return nil, nil - } - - // Prepare the response - respData := map[string]interface{}{ - "id": entity.ID, - "name": entity.Name, - } - - var aliasIDs []string - for _, alias := range entity.Aliases { - aliasIDs = append(aliasIDs, alias.ID) - } - - respData["aliases"] = aliasIDs - - // Return ID of the entity that was either created or updated along with - // its aliases - return &logical.Response{ - Data: respData, - }, nil + return i.EntityUpdateCommon(ctx, d) } } diff --git a/vault/identity_store_entities_update.go b/vault/identity_store_entities_update.go new file mode 100644 index 0000000000..275f7f4ba0 --- /dev/null +++ b/vault/identity_store_entities_update.go @@ -0,0 +1,249 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package vault + +import ( + "context" + "fmt" + + "github.com/hashicorp/vault/helper/identity" + "github.com/hashicorp/vault/sdk/framework" + "github.com/hashicorp/vault/sdk/helper/strutil" + "github.com/hashicorp/vault/sdk/logical" +) + +// EntityBuilder is used to construct or update an identity.Entity. +type EntityBuilder struct { + store *IdentityStore + entity *identity.Entity + isNew bool + err error +} + +// NewEntityBuilder creates a new builder instance. +func NewEntityBuilder(store *IdentityStore) *EntityBuilder { + return &EntityBuilder{ + store: store, + entity: new(identity.Entity), + isNew: true, // Assume new until an existing entity is loaded + } +} + +// WithExistingEntity allows you to pass in a preloaded entity. +func (b *EntityBuilder) WithExistingEntity(entity *identity.Entity) *EntityBuilder { + if b.err != nil || entity == nil { + return b + } + b.entity = entity + b.isNew = false + return b +} + +// WithID attempts to load an entity by its ID. +func (b *EntityBuilder) WithID(id string) *EntityBuilder { + if b.err != nil || id == "" { + return b + } + + entity, err := b.store.MemDBEntityByID(id, true) + if err != nil { + b.err = err + return b + } + if entity == nil { + b.err = fmt.Errorf("entity not found from id: %s", id) + return b + } + + b.entity = entity + b.isNew = false + return b +} + +// WithExternalID handles logic related to the external_id. +func (b *EntityBuilder) WithExternalID(ctx context.Context, externalID string) *EntityBuilder { + if b.err != nil || externalID == "" { + return b + } + + entityByExternalID, err := b.store.MemDBEntityByExternalID(ctx, externalID, true) + if err != nil { + b.err = err + return b + } + + if entityByExternalID != nil { + // An entity with this external ID already exists, so we'll update it. + b.entity = entityByExternalID + b.isNew = false + } else { + // No entity found, so we're just setting the external ID on the current one. + b.entity.ExternalID = externalID + } + + return b +} + +// WithName handles the complex logic for finding an entity by name and checking for conflicts. +func (b *EntityBuilder) WithName(ctx context.Context, name string) *EntityBuilder { + if b.err != nil || name == "" { + return b + } + + entityByName, err := b.store.MemDBEntityByName(ctx, name, true) + if err != nil { + b.err = err + return b + } + + switch { + case entityByName == nil: + // Not found, safe to use this name. + case b.isNew: + // We haven't loaded an entity yet, but one with this name exists. Let's update it. + b.entity = entityByName + b.isNew = false + case b.entity.ID == entityByName.ID: + // The loaded entity and the one found by name are the same. No-op. + default: + // A different entity already has this name, which is a conflict. + b.err = fmt.Errorf("entity name '%s' is already in use", name) + return b + } + + b.entity.Name = name + return b +} + +// WithPolicies sets the policies for the entity. +func (b *EntityBuilder) WithPolicies(policies []string) *EntityBuilder { + if b.err != nil { + return b + } + if strutil.StrListContainsCaseInsensitive(policies, "root") { + b.err = fmt.Errorf("policies cannot contain root") + return b + } + b.entity.Policies = strutil.RemoveDuplicates(policies, false) + return b +} + +// WithDisabled sets the disabled status of the entity. +func (b *EntityBuilder) WithDisabled(disabled bool) *EntityBuilder { + if b.err != nil { + return b + } + b.entity.Disabled = disabled + return b +} + +// WithMetadata sets the metadata for the entity. +func (b *EntityBuilder) WithMetadata(metadata map[string]string) *EntityBuilder { + if b.err != nil { + return b + } + b.entity.Metadata = metadata + return b +} + +// WithSCIMClientID sets the SCIM client ID. +func (b *EntityBuilder) WithSCIMClientID(scimClientID string) *EntityBuilder { + if b.err != nil { + return b + } + b.entity.ScimClientID = scimClientID + return b +} + +// Build finalizes the entity creation/update process. +func (b *EntityBuilder) Build(ctx context.Context) (*logical.Response, error) { + // If any previous step set an error, return it immediately. + if b.err != nil { + return logical.ErrorResponse(b.err.Error()), nil + } + + // Sanitize and persist the entity + if err := b.store.sanitizeEntity(ctx, b.entity); err != nil { + return nil, err + } + if err := b.store.upsertEntity(ctx, b.entity, nil, true); err != nil { + return nil, err + } + + // If this was an update to an existing entity, return 204 No Content + if !b.isNew { + return &logical.Response{}, nil + } + + // This was a new entity, so prepare the response data + respData := map[string]interface{}{ + "id": b.entity.ID, + "name": b.entity.Name, + } + var aliasIDs []string + for _, alias := range b.entity.Aliases { + aliasIDs = append(aliasIDs, alias.ID) + } + respData["aliases"] = aliasIDs + + return &logical.Response{ + Data: respData, + }, nil +} + +// FromFieldData is a convenience method to populate the builder from a FieldData object. +func (b *EntityBuilder) FromFieldData(ctx context.Context, d *framework.FieldData) *EntityBuilder { + if b.err != nil { + return b + } + + if id, ok := d.GetOk("id"); ok { + b.WithID(id.(string)) + } + if externalID, ok := d.GetOk("external_id"); ok { + b.WithExternalID(ctx, externalID.(string)) + } + if name, ok := d.GetOk("name"); ok { + b.WithName(ctx, name.(string)) + } + if scimClientID, ok := d.GetOk("scim_client_id"); ok { + b.WithSCIMClientID(scimClientID.(string)) + } + if policies, ok := d.GetOk("policies"); ok { + b.WithPolicies(policies.([]string)) + } + if disabled, ok := d.GetOk("disabled"); ok { + b.WithDisabled(disabled.(bool)) + } + if metadata, ok, err := d.GetOkErr("metadata"); err != nil { + b.err = fmt.Errorf("failed to parse metadata: %v", err) + } else if ok { + b.WithMetadata(metadata.(map[string]string)) + } + + return b +} + +// Upsert finalizes the entity update process by persisting it to storage. +func (b *EntityBuilder) Upsert(ctx context.Context) (*identity.Entity, error) { + if b.err != nil { + return nil, b.err + } + + // Sanitize and persist the entity + if err := b.store.sanitizeEntity(ctx, b.entity); err != nil { + return nil, err + } + if err := b.store.upsertEntity(ctx, b.entity, nil, true); err != nil { + return nil, err + } + + return b.entity, nil +} + +func (i *IdentityStore) EntityUpdateCommon(ctx context.Context, d *framework.FieldData) (*logical.Response, error) { + return NewEntityBuilder(i). + FromFieldData(ctx, d). + Build(ctx) +} diff --git a/vault/identity_store_groups.go b/vault/identity_store_groups.go index 4955d86e10..c475c566c6 100644 --- a/vault/identity_store_groups.go +++ b/vault/identity_store_groups.go @@ -35,6 +35,10 @@ func groupPathFields() map[string]*framework.FieldSchema { Type: framework.TypeString, Description: "Name of the group.", }, + "scim_client_id": { + Type: framework.TypeString, + Description: "SCIM Client ID of the entity", + }, "metadata": { Type: framework.TypeKVPairs, Description: `Metadata to be associated with the group. @@ -309,6 +313,12 @@ func (i *IdentityStore) handleGroupUpdateCommon(ctx context.Context, req *logica group.Name = groupName } + _, ok = d.Schema["scim_client_id"] + if ok { + entitSCIMClientID := d.Get("scim_client_id").(string) + group.ScimClientID = entitSCIMClientID + } + metadata, ok, err := d.GetOkErr("metadata") if err != nil { return logical.ErrorResponse(fmt.Sprintf("failed to parse metadata: %v", err)), nil diff --git a/vault/identity_store_schema.go b/vault/identity_store_schema.go index c05c962276..c4e986cbba 100644 --- a/vault/identity_store_schema.go +++ b/vault/identity_store_schema.go @@ -28,6 +28,7 @@ func identityStoreSchema(lowerCaseName bool) *memdb.DBSchema { groupsTableSchema, groupAliasesTableSchema, oidcClientsTableSchema, + scimConfigSchema, } for _, schemaFunc := range schemas { @@ -130,6 +131,33 @@ func entitiesTableSchema(lowerCaseName bool) *memdb.TableSchema { Field: "NamespaceID", }, }, + // SCIM-related indexes + "external_id": { + Name: "external_id", + Unique: true, + Indexer: &memdb.CompoundIndex{ + Indexes: []memdb.Indexer{ + &memdb.StringFieldIndex{ + Field: "NamespaceID", + }, + &memdb.StringFieldIndex{ + Field: "ExternalID", + }, + }, + }, + AllowMissing: true, + }, + + "scim_client_id": { + Name: "scim_client_id", + AllowMissing: true, + Indexer: &memdb.CompoundIndex{ + Indexes: []memdb.Indexer{ + &memdb.StringFieldIndex{Field: "NamespaceID"}, + &memdb.StringFieldIndex{Field: "ScimClientID"}, + }, + }, + }, }, } } @@ -186,6 +214,13 @@ func groupsTableSchema(lowerCaseName bool) *memdb.TableSchema { Field: "NamespaceID", }, }, + "scim_client_id": { + Name: "scim_client_id", + Indexer: &memdb.StringFieldIndex{ + Field: "ScimClientID", + }, + AllowMissing: true, + }, }, } } diff --git a/vault/identity_store_scim_schema.go b/vault/identity_store_scim_schema.go new file mode 100644 index 0000000000..3706693933 --- /dev/null +++ b/vault/identity_store_scim_schema.go @@ -0,0 +1,59 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package vault + +import "github.com/hashicorp/go-memdb" + +const ( + scimConfigTable = "scim_config" +) + +func scimConfigSchema(_ bool) *memdb.TableSchema { + return &memdb.TableSchema{ + Name: scimConfigTable, + Indexes: map[string]*memdb.IndexSchema{ + "id": { + Name: "id", + Unique: true, + Indexer: &memdb.StringFieldIndex{ + Field: "ID", + }, + }, + "client_id": { + Name: "client_id", + Unique: true, + Indexer: &memdb.StringFieldIndex{ + Field: "ClientID", + }, + }, + "client_role": { + Name: "client_role", + Indexer: &memdb.StringFieldIndex{ + Field: "ClientRole", + }, + }, + "access_grant_method": { + Name: "access_grant_method", + Indexer: &memdb.StringFieldIndex{ + Field: "AccessGrantMethod", + }, + }, + "access_grant_principal": { + Name: "access_grant_principal", + Unique: true, + Indexer: &memdb.StringFieldIndex{ + Field: "AccessGrantPrincipal", + }, + }, + "alias_mount_accessor": { + Name: "alias_mount_accessor", + Unique: true, + Indexer: &memdb.StringFieldIndex{ + Field: "AliasMountAccessor", + }, + AllowMissing: true, + }, + }, + } +} diff --git a/vault/identity_store_structs.go b/vault/identity_store_structs.go index bf4c1abb92..b528d945ac 100644 --- a/vault/identity_store_structs.go +++ b/vault/identity_store_structs.go @@ -100,6 +100,10 @@ type IdentityStore struct { // buckets groupPacker *storagepacker.StoragePacker + // groupPacker is used to pack multiple group storage entries into 256 + // buckets + scimConfigPacker *storagepacker.StoragePacker + // disableLowerCaseNames indicates whether or not identity artifacts are // operated case insensitively disableLowerCasedNames bool diff --git a/vault/identity_store_util.go b/vault/identity_store_util.go index 9b1b1a7933..3255e43968 100644 --- a/vault/identity_store_util.go +++ b/vault/identity_store_util.go @@ -8,6 +8,7 @@ import ( "errors" "fmt" "math/rand" + "sort" "strings" "sync" "testing" @@ -1467,6 +1468,42 @@ func (i *IdentityStore) MemDBEntityByIDInTxn(txn *memdb.Txn, entityID string, cl return entity, nil } +func (i *IdentityStore) MemDBEntityByExternalIDInTxn(ctx context.Context, txn *memdb.Txn, externalID string, clone bool) (*identity.Entity, error) { + var entity *identity.Entity + if externalID == "" { + return nil, fmt.Errorf("missing entity id") + } + + if txn == nil { + return nil, fmt.Errorf("txn is nil") + } + + ns, err := namespace.FromContext(ctx) + if err != nil { + return nil, err + } + + entityRaw, err := txn.First(entitiesTable, "external_id", ns.ID, externalID) + if err != nil { + return nil, fmt.Errorf("failed to fetch entity from memdb using entity id: %w", err) + } + + if entityRaw == nil { + return entity, nil + } + + entity, ok := entityRaw.(*identity.Entity) + if !ok { + return nil, fmt.Errorf("failed to declare the type of fetched entity") + } + + if clone { + return entity.Clone() + } + + return entity, nil +} + func (i *IdentityStore) MemDBEntityByID(entityID string, clone bool) (*identity.Entity, error) { if entityID == "" { return nil, fmt.Errorf("missing entity id") @@ -1477,6 +1514,16 @@ func (i *IdentityStore) MemDBEntityByID(entityID string, clone bool) (*identity. return i.MemDBEntityByIDInTxn(txn, entityID, clone) } +func (i *IdentityStore) MemDBEntityByExternalID(ctx context.Context, externalID string, clone bool) (*identity.Entity, error) { + if externalID == "" { + return nil, fmt.Errorf("missing entity externalID") + } + + txn := i.db.Txn(false) + + return i.MemDBEntityByExternalIDInTxn(ctx, txn, externalID, clone) +} + func (i *IdentityStore) MemDBEntityByName(ctx context.Context, entityName string, clone bool) (*identity.Entity, error) { if entityName == "" { return nil, fmt.Errorf("missing entity name") @@ -1659,6 +1706,98 @@ func (i *IdentityStore) MemDBDeleteEntityByID(entityID string) error { return nil } +func (i *IdentityStore) MemDBEntitiesByScimClientID(ctx context.Context, scimClientID string, maxResultSet int) ([]*identity.Entity, error) { + if scimClientID == "" { + return nil, nil + } + + txn := i.db.Txn(false) + defer txn.Abort() + + entities, err := i.MemDBEntitiesByScimClientIDInTxn(ctx, txn, scimClientID, maxResultSet) + if err != nil { + return nil, err + } + + return entities, nil +} + +// MemDBEntitiesByScimClientIDInTxn retrieves a fully sorted list of all entities +// belonging to a specific SCIM client. +// +// Implementation Pattern: Filter-Then-Sort +// This function implements the "filter then sort" pattern, which is the idiomatic +// approach for this type of query in go-memdb. The process involves two steps: +// 1. Filtering: A simple two-part compound index on `(NamespaceID, ScimClientID)` +// is used with `txn.Get()` to efficiently retrieve all entities for the client. +// 2. Sorting: The resulting slice of entities is then sorted in-memory by name +// using Go's native `sort.Slice`. +// +// Why This Pattern is Used (memdb Limitations): +// It might seem more efficient to perform the sorting at the database level +// using a single ordered index, memdb's built-in `CompoundIndex` does not support +// using a partial key (e.g., just `NamespaceID` and `ScimClientID`) to get an +// ordered iterator over the remaining fields (e.g., `Name`). The indexer's +// internal `FromArgs` method requires a value for every field defined in the index. +// This makes a single-operation "filter and ordered scan" impossible with the +// standard indexers. +// +// Given that go-memdb is an in-memory database, sorting a pre-filtered slice +// is extremely fast and avoids the complexity of creating a custom indexer. +// This function returns the entire sorted list; the caller is responsible for +// any subsequent pagination. +func (i *IdentityStore) MemDBEntitiesByScimClientIDInTxn(ctx context.Context, txn *memdb.Txn, scimClientID string, maxResultSet int) ([]*identity.Entity, error) { + if txn == nil { + return nil, fmt.Errorf("nil txn") + } + + if scimClientID == "" { + return nil, fmt.Errorf("empty scim client id key") + } + + ns, err := namespace.FromContext(ctx) + if err != nil { + return nil, err + } + + ws := memdb.NewWatchSet() + + entitiesIter, err := txn.Get(entitiesTable, "scim_client_id", ns.ID, scimClientID) + if err != nil { + return nil, fmt.Errorf("failed to lookup entities using scim client id: %w", err) + } + + ws.Add(entitiesIter.WatchCh()) + + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + break + } + + var entities []*identity.Entity + for item := entitiesIter.Next(); item != nil; item = entitiesIter.Next() { + if len(entities) > maxResultSet { + return nil, fmt.Errorf("query returned more than the server's limit of %d results. Please use more specific filters", maxResultSet) + } + + entity, err := item.(*identity.Entity).Clone() + if err != nil { + return nil, err + } + entities = append(entities, entity) + } + + sort.Slice(entities, func(i, j int) bool { + return entities[i].Name < entities[j].Name + }) + + return entities, nil + } +} + // FetchEntityForLocalAliasInTxn fetches the entity associated with the provided // local identity.Alias. MemDB will first be searched for the entity. If it is // not found there, the localAliasPacker storagepacker.StoragePacker will be @@ -2344,6 +2483,43 @@ func (i *IdentityStore) MemDBGroupByID(groupID string, clone bool) (*identity.Gr return i.MemDBGroupByIDInTxn(txn, groupID, clone) } +func (i *IdentityStore) MemDBGroupsByScimClientIDInTxn(txn *memdb.Txn, scimClientID string) ([]*identity.Group, error) { + if scimClientID == "" { + return nil, fmt.Errorf("missing scim client ID") + } + + if txn == nil { + return nil, fmt.Errorf("txn is nil") + } + + groupsIter, err := txn.Get(groupsTable, "scim_client_id", scimClientID) + if err != nil { + return nil, fmt.Errorf("failed to lookup groups using scim client ID: %w", err) + } + + var groups []*identity.Group + for group := groupsIter.Next(); group != nil; group = groupsIter.Next() { + entry := group.(*identity.Group) + entry, err = entry.Clone() + if err != nil { + return nil, err + } + groups = append(groups, entry) + } + + return groups, nil +} + +func (i *IdentityStore) MemDBGroupsByScimClientID(scimClientID string) ([]*identity.Group, error) { + if scimClientID == "" { + return nil, fmt.Errorf("missing scim client ID") + } + + txn := i.db.Txn(false) + + return i.MemDBGroupsByScimClientIDInTxn(txn, scimClientID) +} + func (i *IdentityStore) MemDBGroupsByParentGroupIDInTxn(txn *memdb.Txn, memberGroupID string, clone bool) ([]*identity.Group, error) { if memberGroupID == "" { return nil, fmt.Errorf("missing member group ID") diff --git a/vault/namespaces.go b/vault/namespaces.go index 4b82a5e9fe..0280c14e57 100644 --- a/vault/namespaces.go +++ b/vault/namespaces.go @@ -18,6 +18,13 @@ func namespaceByID(ctx context.Context, nsID string, c *Core) (*namespace.Namesp return nil, namespace.ErrNoNamespace } +func namespaceByPath(ctx context.Context, nsPath string, c *Core) (*namespace.Namespace, error) { + if nsPath == "" { + return namespace.RootNamespace, nil + } + return nil, namespace.ErrNoNamespace +} + var NamespaceRegister func(context.Context, *namespace.Namespace, *Core) error = namespaceRegister func namespaceRegister(ctx context.Context, ns *namespace.Namespace, c *Core) error {