feat(repository): apply retention policies server-side (#3249)

* feat(repository): apply retention policies server-side

This allows append-only snapshots where the client can never delete
arbitrary manifests and policies are maintained on the server.

The client only needs permissions to create snapshots in a given, which
automatically gives them permission to invoke the server-side method
for their own snapshots only.

* Update cli/command_acl_add.go

Co-authored-by: Guillaume <Gui13@users.noreply.github.com>

* Update internal/server/api_manifest.go

Co-authored-by: Guillaume <Gui13@users.noreply.github.com>

* Update internal/server/api_manifest.go

Co-authored-by: Guillaume <Gui13@users.noreply.github.com>

* Update internal/server/grpc_session.go

Co-authored-by: Guillaume <Gui13@users.noreply.github.com>

---------

Co-authored-by: Guillaume <Gui13@users.noreply.github.com>
This commit is contained in:
Jarek Kowalski
2023-09-02 18:23:21 -07:00
committed by GitHub
parent 80423cf5f6
commit 044db7593b
18 changed files with 688 additions and 228 deletions

View File

@@ -11,9 +11,10 @@
)
type commandACLAdd struct {
user string
target string
level string
user string
target string
level string
overwrite bool
}
func (c *commandACLAdd) setup(svc appServices, parent commandParent) {
@@ -21,6 +22,7 @@ func (c *commandACLAdd) setup(svc appServices, parent commandParent) {
cmd.Flag("user", "User the ACL targets").Required().StringVar(&c.user)
cmd.Flag("target", "Manifests targeted by the rule (type:T,key1:value1,...,keyN:valueN)").Required().StringVar(&c.target)
cmd.Flag("access", "Access the user gets to subject").Required().EnumVar(&c.level, acl.SupportedAccessLevels()...)
cmd.Flag("overwrite", "Overwrite existing rule with the same user and target").BoolVar(&c.overwrite)
cmd.Action(svc.repositoryWriterAction(c.run))
}
@@ -47,5 +49,5 @@ func (c *commandACLAdd) run(ctx context.Context, rep repo.RepositoryWriter) erro
Access: al,
}
return errors.Wrap(acl.AddACL(ctx, rep, e), "error adding ACL entry")
return errors.Wrap(acl.AddACL(ctx, rep, e, c.overwrite), "error adding ACL entry")
}

View File

@@ -41,7 +41,7 @@ func (c *commandACLEnable) run(ctx context.Context, rep repo.RepositoryWriter) e
}
for _, e := range auth.DefaultACLs {
if err := acl.AddACL(ctx, rep, e); err != nil {
if err := acl.AddACL(ctx, rep, e, false); err != nil {
return errors.Wrap(err, "unable to add default ACL")
}
}

View File

@@ -71,10 +71,6 @@ func (c *commandSnapshotExpire) run(ctx context.Context, rep repo.RepositoryWrit
log(ctx).Infof("Deleted %v snapshots of %v...", len(deleted), src)
} else {
log(ctx).Infof("%v snapshot(s) of %v would be deleted. Pass --delete to do it.", len(deleted), src)
for _, it := range deleted {
log(ctx).Infof(" %v", formatTimestamp(it.StartTime.ToTime()))
}
}
}

View File

@@ -6,6 +6,7 @@
"strings"
"github.com/pkg/errors"
"golang.org/x/exp/maps"
"github.com/kopia/kopia/repo"
"github.com/kopia/kopia/repo/manifest"
@@ -109,11 +110,28 @@ func LoadEntries(ctx context.Context, rep repo.Repository, old []*Entry) ([]*Ent
}
// AddACL validates and adds the specified ACL entry to the repository.
func AddACL(ctx context.Context, w repo.RepositoryWriter, e *Entry) error {
func AddACL(ctx context.Context, w repo.RepositoryWriter, e *Entry, overwrite bool) error {
if err := e.Validate(); err != nil {
return errors.Wrap(err, "error validating ACL")
}
entries, err := LoadEntries(ctx, w, nil)
if err != nil {
return errors.Wrap(err, "unable to load ACL entries")
}
for _, oldE := range entries {
if e.User == oldE.User && maps.Equal(e.Target, oldE.Target) {
if !overwrite && e.Access < oldE.Access {
return errors.Errorf("ACL entry for a given user and target already exists %v: %v", oldE.User, oldE.Target)
}
if err = w.DeleteManifest(ctx, oldE.ManifestID); err != nil {
return errors.Wrap(err, "error deleting old")
}
}
}
manifestID, err := w.PutManifest(ctx, map[string]string{
manifest.TypeLabelKey: aclManifestType,
}, e)

View File

@@ -175,7 +175,7 @@ func TestLoadEntries(t *testing.T) {
Access: acl.AccessLevelFull,
}
require.NoError(t, acl.AddACL(ctx, env.RepositoryWriter, e1))
require.NoError(t, acl.AddACL(ctx, env.RepositoryWriter, e1, false))
entries, err = acl.LoadEntries(ctx, env.RepositoryWriter, entries)
require.NoError(t, err)
@@ -192,7 +192,7 @@ func TestLoadEntries(t *testing.T) {
Access: acl.AccessLevelFull,
}
require.NoError(t, acl.AddACL(ctx, env.RepositoryWriter, e2))
require.NoError(t, acl.AddACL(ctx, env.RepositoryWriter, e2, false))
entries, err = acl.LoadEntries(ctx, env.RepositoryWriter, entries)
require.NoError(t, err)

View File

@@ -109,7 +109,7 @@ func TestDefaultAuthorizer_DefaultACLs(t *testing.T) {
ctx, env := repotesting.NewEnvironment(t, repotesting.FormatNotImportant)
for _, e := range auth.DefaultACLs {
require.NoError(t, acl.AddACL(ctx, env.RepositoryWriter, e))
require.NoError(t, acl.AddACL(ctx, env.RepositoryWriter, e, false))
}
verifyLegacyAuthorizer(ctx, t, env.Repository, auth.DefaultAuthorizer())

View File

@@ -1357,6 +1357,108 @@ func (x *PrefetchContentsResponse) GetContentIds() []string {
return nil
}
type ApplyRetentionPolicyRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
SourcePath string `protobuf:"bytes,1,opt,name=source_path,json=sourcePath,proto3" json:"source_path,omitempty"`
ReallyDelete bool `protobuf:"varint,2,opt,name=really_delete,json=reallyDelete,proto3" json:"really_delete,omitempty"`
}
func (x *ApplyRetentionPolicyRequest) Reset() {
*x = ApplyRetentionPolicyRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_repository_server_proto_msgTypes[24]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ApplyRetentionPolicyRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ApplyRetentionPolicyRequest) ProtoMessage() {}
func (x *ApplyRetentionPolicyRequest) ProtoReflect() protoreflect.Message {
mi := &file_repository_server_proto_msgTypes[24]
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 ApplyRetentionPolicyRequest.ProtoReflect.Descriptor instead.
func (*ApplyRetentionPolicyRequest) Descriptor() ([]byte, []int) {
return file_repository_server_proto_rawDescGZIP(), []int{24}
}
func (x *ApplyRetentionPolicyRequest) GetSourcePath() string {
if x != nil {
return x.SourcePath
}
return ""
}
func (x *ApplyRetentionPolicyRequest) GetReallyDelete() bool {
if x != nil {
return x.ReallyDelete
}
return false
}
type ApplyRetentionPolicyResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
ManifestIds []string `protobuf:"bytes,1,rep,name=manifest_ids,json=manifestIds,proto3" json:"manifest_ids,omitempty"`
}
func (x *ApplyRetentionPolicyResponse) Reset() {
*x = ApplyRetentionPolicyResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_repository_server_proto_msgTypes[25]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ApplyRetentionPolicyResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ApplyRetentionPolicyResponse) ProtoMessage() {}
func (x *ApplyRetentionPolicyResponse) ProtoReflect() protoreflect.Message {
mi := &file_repository_server_proto_msgTypes[25]
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 ApplyRetentionPolicyResponse.ProtoReflect.Descriptor instead.
func (*ApplyRetentionPolicyResponse) Descriptor() ([]byte, []int) {
return file_repository_server_proto_rawDescGZIP(), []int{25}
}
func (x *ApplyRetentionPolicyResponse) GetManifestIds() []string {
if x != nil {
return x.ManifestIds
}
return nil
}
type SessionRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@@ -1379,13 +1481,14 @@ type SessionRequest struct {
// *SessionRequest_FindManifests
// *SessionRequest_DeleteManifest
// *SessionRequest_PrefetchContents
// *SessionRequest_ApplyRetentionPolicy
Request isSessionRequest_Request `protobuf_oneof:"request"`
}
func (x *SessionRequest) Reset() {
*x = SessionRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_repository_server_proto_msgTypes[24]
mi := &file_repository_server_proto_msgTypes[26]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -1398,7 +1501,7 @@ func (x *SessionRequest) String() string {
func (*SessionRequest) ProtoMessage() {}
func (x *SessionRequest) ProtoReflect() protoreflect.Message {
mi := &file_repository_server_proto_msgTypes[24]
mi := &file_repository_server_proto_msgTypes[26]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1411,7 +1514,7 @@ func (x *SessionRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use SessionRequest.ProtoReflect.Descriptor instead.
func (*SessionRequest) Descriptor() ([]byte, []int) {
return file_repository_server_proto_rawDescGZIP(), []int{24}
return file_repository_server_proto_rawDescGZIP(), []int{26}
}
func (x *SessionRequest) GetRequestId() int64 {
@@ -1505,6 +1608,13 @@ func (x *SessionRequest) GetPrefetchContents() *PrefetchContentsRequest {
return nil
}
func (x *SessionRequest) GetApplyRetentionPolicy() *ApplyRetentionPolicyRequest {
if x, ok := x.GetRequest().(*SessionRequest_ApplyRetentionPolicy); ok {
return x.ApplyRetentionPolicy
}
return nil
}
type isSessionRequest_Request interface {
isSessionRequest_Request()
}
@@ -1549,6 +1659,10 @@ type SessionRequest_PrefetchContents struct {
PrefetchContents *PrefetchContentsRequest `protobuf:"bytes,19,opt,name=prefetch_contents,json=prefetchContents,proto3,oneof"`
}
type SessionRequest_ApplyRetentionPolicy struct {
ApplyRetentionPolicy *ApplyRetentionPolicyRequest `protobuf:"bytes,20,opt,name=apply_retention_policy,json=applyRetentionPolicy,proto3,oneof"`
}
func (*SessionRequest_InitializeSession) isSessionRequest_Request() {}
func (*SessionRequest_GetContentInfo) isSessionRequest_Request() {}
@@ -1569,13 +1683,15 @@ func (*SessionRequest_DeleteManifest) isSessionRequest_Request() {}
func (*SessionRequest_PrefetchContents) isSessionRequest_Request() {}
func (*SessionRequest_ApplyRetentionPolicy) isSessionRequest_Request() {}
type SessionResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
RequestId int64 `protobuf:"varint,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` // corresponds to request ID
HasMore bool `protobuf:"varint,3,opt,name=has_more,json=hasMore,proto3" json:"has_more,omitempty"`
HasMore bool `protobuf:"varint,3,opt,name=has_more,json=hasMore,proto3" json:"has_more,omitempty"` // if set to true, the client should expect more responses with the same request_id.
// Types that are assignable to Response:
// *SessionResponse_Error
// *SessionResponse_InitializeSession
@@ -1588,13 +1704,14 @@ type SessionResponse struct {
// *SessionResponse_FindManifests
// *SessionResponse_DeleteManifest
// *SessionResponse_PrefetchContents
// *SessionResponse_ApplyRetentionPolicy
Response isSessionResponse_Response `protobuf_oneof:"response"`
}
func (x *SessionResponse) Reset() {
*x = SessionResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_repository_server_proto_msgTypes[25]
mi := &file_repository_server_proto_msgTypes[27]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -1607,7 +1724,7 @@ func (x *SessionResponse) String() string {
func (*SessionResponse) ProtoMessage() {}
func (x *SessionResponse) ProtoReflect() protoreflect.Message {
mi := &file_repository_server_proto_msgTypes[25]
mi := &file_repository_server_proto_msgTypes[27]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1620,7 +1737,7 @@ func (x *SessionResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use SessionResponse.ProtoReflect.Descriptor instead.
func (*SessionResponse) Descriptor() ([]byte, []int) {
return file_repository_server_proto_rawDescGZIP(), []int{25}
return file_repository_server_proto_rawDescGZIP(), []int{27}
}
func (x *SessionResponse) GetRequestId() int64 {
@@ -1721,6 +1838,13 @@ func (x *SessionResponse) GetPrefetchContents() *PrefetchContentsResponse {
return nil
}
func (x *SessionResponse) GetApplyRetentionPolicy() *ApplyRetentionPolicyResponse {
if x, ok := x.GetResponse().(*SessionResponse_ApplyRetentionPolicy); ok {
return x.ApplyRetentionPolicy
}
return nil
}
type isSessionResponse_Response interface {
isSessionResponse_Response()
}
@@ -1769,6 +1893,10 @@ type SessionResponse_PrefetchContents struct {
PrefetchContents *PrefetchContentsResponse `protobuf:"bytes,19,opt,name=prefetch_contents,json=prefetchContents,proto3,oneof"`
}
type SessionResponse_ApplyRetentionPolicy struct {
ApplyRetentionPolicy *ApplyRetentionPolicyResponse `protobuf:"bytes,20,opt,name=apply_retention_policy,json=applyRetentionPolicy,proto3,oneof"`
}
func (*SessionResponse_Error) isSessionResponse_Response() {}
func (*SessionResponse_InitializeSession) isSessionResponse_Response() {}
@@ -1791,6 +1919,8 @@ func (*SessionResponse_DeleteManifest) isSessionResponse_Response() {}
func (*SessionResponse_PrefetchContents) isSessionResponse_Response() {}
func (*SessionResponse_ApplyRetentionPolicy) isSessionResponse_Response() {}
var File_repository_server_proto protoreflect.FileDescriptor
var file_repository_server_proto_rawDesc = []byte{
@@ -1952,139 +2082,162 @@ func (*SessionResponse_PrefetchContents) isSessionResponse_Response() {}
0x0a, 0x18, 0x50, 0x72, 0x65, 0x66, 0x65, 0x74, 0x63, 0x68, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e,
0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6f,
0x6e, 0x74, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52,
0x0a, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x73, 0x22, 0xe9, 0x07, 0x0a, 0x0e,
0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d,
0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01,
0x28, 0x03, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x57, 0x0a,
0x0d, 0x74, 0x72, 0x61, 0x63, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x18, 0x02,
0x20, 0x03, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x70,
0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x74,
0x65, 0x78, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0c, 0x74, 0x72, 0x61, 0x63, 0x65, 0x43,
0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x12, 0x5b, 0x0a, 0x12, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61,
0x6c, 0x69, 0x7a, 0x65, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x0a, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73,
0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65,
0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00,
0x52, 0x11, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x53, 0x65, 0x73, 0x73,
0x69, 0x6f, 0x6e, 0x12, 0x53, 0x0a, 0x10, 0x67, 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65,
0x6e, 0x74, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e,
0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79,
0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x0e, 0x67, 0x65, 0x74, 0x43, 0x6f, 0x6e,
0x74, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x36, 0x0a, 0x05, 0x66, 0x6c, 0x75, 0x73,
0x68, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f,
0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x46, 0x6c, 0x75, 0x73, 0x68,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x05, 0x66, 0x6c, 0x75, 0x73, 0x68,
0x12, 0x4c, 0x0a, 0x0d, 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e,
0x74, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f,
0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x57, 0x72, 0x69, 0x74, 0x65,
0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00,
0x52, 0x0c, 0x77, 0x72, 0x69, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x12, 0x46,
0x0a, 0x0b, 0x67, 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x0e, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x70, 0x6f,
0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e,
0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x0a, 0x67, 0x65, 0x74, 0x43,
0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x12, 0x49, 0x0a, 0x0c, 0x67, 0x65, 0x74, 0x5f, 0x6d, 0x61,
0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x6b,
0x0a, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x73, 0x22, 0x63, 0x0a, 0x1b, 0x41,
0x70, 0x70, 0x6c, 0x79, 0x52, 0x65, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x6f, 0x6c,
0x69, 0x63, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x6f,
0x75, 0x72, 0x63, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
0x0a, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x50, 0x61, 0x74, 0x68, 0x12, 0x23, 0x0a, 0x0d, 0x72,
0x65, 0x61, 0x6c, 0x6c, 0x79, 0x5f, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01,
0x28, 0x08, 0x52, 0x0c, 0x72, 0x65, 0x61, 0x6c, 0x6c, 0x79, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65,
0x22, 0x41, 0x0a, 0x1c, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x52, 0x65, 0x74, 0x65, 0x6e, 0x74, 0x69,
0x6f, 0x6e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x73,
0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x6d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74,
0x49, 0x64, 0x73, 0x22, 0xd0, 0x08, 0x0a, 0x0e, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x57, 0x0a, 0x0d, 0x74, 0x72, 0x61, 0x63, 0x65, 0x5f, 0x63,
0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x6b,
0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e,
0x47, 0x65, 0x74, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x48, 0x00, 0x52, 0x0b, 0x67, 0x65, 0x74, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73,
0x74, 0x12, 0x49, 0x0a, 0x0c, 0x70, 0x75, 0x74, 0x5f, 0x6d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73,
0x74, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f,
0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x50, 0x75, 0x74, 0x4d, 0x61,
0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52,
0x0b, 0x70, 0x75, 0x74, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x12, 0x4f, 0x0a, 0x0e,
0x66, 0x69, 0x6e, 0x64, 0x5f, 0x6d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x73, 0x18, 0x11,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x70,
0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x46, 0x69, 0x6e, 0x64, 0x4d, 0x61, 0x6e, 0x69,
0x66, 0x65, 0x73, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x0d,
0x66, 0x69, 0x6e, 0x64, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x73, 0x12, 0x52, 0x0a,
0x0f, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x5f, 0x6d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74,
0x18, 0x12, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72,
0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65,
0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48,
0x00, 0x52, 0x0e, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73,
0x74, 0x12, 0x58, 0x0a, 0x11, 0x70, 0x72, 0x65, 0x66, 0x65, 0x74, 0x63, 0x68, 0x5f, 0x63, 0x6f,
0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x13, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x6b,
0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e,
0x50, 0x72, 0x65, 0x66, 0x65, 0x74, 0x63, 0x68, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x10, 0x70, 0x72, 0x65, 0x66, 0x65,
0x74, 0x63, 0x68, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x1a, 0x3f, 0x0a, 0x11, 0x54,
0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x54,
0x72, 0x61, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 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, 0x09, 0x0a, 0x07,
0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xaf, 0x07, 0x0a, 0x0f, 0x53, 0x65, 0x73, 0x73,
0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x72,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52,
0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x68, 0x61,
0x73, 0x5f, 0x6d, 0x6f, 0x72, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x68, 0x61,
0x73, 0x4d, 0x6f, 0x72, 0x65, 0x12, 0x37, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x70,
0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x5c,
0x52, 0x0c, 0x74, 0x72, 0x61, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x12, 0x5b,
0x0a, 0x12, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x5f, 0x73, 0x65, 0x73,
0x73, 0x69, 0x6f, 0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x6b, 0x6f, 0x70,
0x73, 0x69, 0x6f, 0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x6b, 0x6f, 0x70,
0x69, 0x61, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x49, 0x6e,
0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x11, 0x69, 0x6e, 0x69, 0x74, 0x69,
0x61, 0x6c, 0x69, 0x7a, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x54, 0x0a, 0x10,
0x67, 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x6e, 0x66, 0x6f,
0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72,
0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e,
0x74, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x48, 0x00, 0x52, 0x0e, 0x67, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x49, 0x6e,
0x66, 0x6f, 0x12, 0x37, 0x0a, 0x05, 0x66, 0x6c, 0x75, 0x73, 0x68, 0x18, 0x0c, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x1f, 0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69,
0x74, 0x6f, 0x72, 0x79, 0x2e, 0x46, 0x6c, 0x75, 0x73, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x48, 0x00, 0x52, 0x05, 0x66, 0x6c, 0x75, 0x73, 0x68, 0x12, 0x4d, 0x0a, 0x0d, 0x77,
0x72, 0x69, 0x74, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x0d, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x26, 0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73,
0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x57, 0x72, 0x69, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65,
0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x0c, 0x77, 0x72,
0x69, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x12, 0x47, 0x0a, 0x0b, 0x67, 0x65,
0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x11, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61,
0x6c, 0x69, 0x7a, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x53, 0x0a, 0x10, 0x67,
0x65, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18,
0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65,
0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x74,
0x65, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00,
0x52, 0x0e, 0x67, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f,
0x12, 0x36, 0x0a, 0x05, 0x66, 0x6c, 0x75, 0x73, 0x68, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x1e, 0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f,
0x72, 0x79, 0x2e, 0x46, 0x6c, 0x75, 0x73, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48,
0x00, 0x52, 0x05, 0x66, 0x6c, 0x75, 0x73, 0x68, 0x12, 0x4c, 0x0a, 0x0d, 0x77, 0x72, 0x69, 0x74,
0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x25, 0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f,
0x72, 0x79, 0x2e, 0x57, 0x72, 0x69, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x0c, 0x77, 0x72, 0x69, 0x74, 0x65, 0x43,
0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x12, 0x46, 0x0a, 0x0b, 0x67, 0x65, 0x74, 0x5f, 0x63, 0x6f,
0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x6b, 0x6f,
0x70, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x47,
0x65, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x48, 0x00, 0x52, 0x0a, 0x67, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x12, 0x49,
0x0a, 0x0c, 0x67, 0x65, 0x74, 0x5f, 0x6d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x18, 0x0f,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x70,
0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x6e, 0x69, 0x66,
0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x0b, 0x67, 0x65,
0x74, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x12, 0x49, 0x0a, 0x0c, 0x70, 0x75, 0x74,
0x5f, 0x6d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x24, 0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f,
0x72, 0x79, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x0a, 0x67, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x74,
0x65, 0x6e, 0x74, 0x12, 0x4a, 0x0a, 0x0c, 0x67, 0x65, 0x74, 0x5f, 0x6d, 0x61, 0x6e, 0x69, 0x66,
0x65, 0x73, 0x74, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x6b, 0x6f, 0x70, 0x69,
0x61, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x47, 0x65, 0x74,
0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x48, 0x00, 0x52, 0x0b, 0x67, 0x65, 0x74, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x12,
0x4a, 0x0a, 0x0c, 0x70, 0x75, 0x74, 0x5f, 0x6d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x18,
0x10, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65,
0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x50, 0x75, 0x74, 0x4d, 0x61, 0x6e, 0x69,
0x66, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x0b,
0x70, 0x75, 0x74, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x12, 0x50, 0x0a, 0x0e, 0x66,
0x69, 0x6e, 0x64, 0x5f, 0x6d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x73, 0x18, 0x11, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x70, 0x6f,
0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x46, 0x69, 0x6e, 0x64, 0x4d, 0x61, 0x6e, 0x69, 0x66,
0x65, 0x73, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x0d,
0x66, 0x69, 0x6e, 0x64, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x73, 0x12, 0x53, 0x0a,
0x0f, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x5f, 0x6d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74,
0x18, 0x12, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72,
0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65,
0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x48, 0x00, 0x52, 0x0e, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65,
0x73, 0x74, 0x12, 0x59, 0x0a, 0x11, 0x70, 0x72, 0x65, 0x66, 0x65, 0x74, 0x63, 0x68, 0x5f, 0x63,
0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x13, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e,
0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79,
0x2e, 0x50, 0x72, 0x65, 0x66, 0x65, 0x74, 0x63, 0x68, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74,
0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x10, 0x70, 0x72, 0x65,
0x66, 0x65, 0x74, 0x63, 0x68, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x42, 0x0a, 0x0a,
0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x65, 0x0a, 0x0f, 0x4b, 0x6f, 0x70,
0x69, 0x61, 0x52, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x52, 0x0a, 0x07,
0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f,
0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x53, 0x65, 0x73, 0x73, 0x69,
0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x6b, 0x6f, 0x70, 0x69,
0x61, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x53, 0x65, 0x73,
0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x30, 0x01,
0x42, 0x29, 0x5a, 0x27, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6b,
0x6f, 0x70, 0x69, 0x61, 0x2f, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72,
0x6e, 0x61, 0x6c, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x61, 0x70, 0x69, 0x62, 0x06, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x33,
0x72, 0x79, 0x2e, 0x50, 0x75, 0x74, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x0b, 0x70, 0x75, 0x74, 0x4d, 0x61, 0x6e, 0x69,
0x66, 0x65, 0x73, 0x74, 0x12, 0x4f, 0x0a, 0x0e, 0x66, 0x69, 0x6e, 0x64, 0x5f, 0x6d, 0x61, 0x6e,
0x69, 0x66, 0x65, 0x73, 0x74, 0x73, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x6b,
0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e,
0x46, 0x69, 0x6e, 0x64, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x73, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x0d, 0x66, 0x69, 0x6e, 0x64, 0x4d, 0x61, 0x6e, 0x69,
0x66, 0x65, 0x73, 0x74, 0x73, 0x12, 0x52, 0x0a, 0x0f, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x5f,
0x6d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x18, 0x12, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27,
0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72,
0x79, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x0e, 0x64, 0x65, 0x6c, 0x65, 0x74,
0x65, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x12, 0x58, 0x0a, 0x11, 0x70, 0x72, 0x65,
0x66, 0x65, 0x74, 0x63, 0x68, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x13,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x70,
0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x50, 0x72, 0x65, 0x66, 0x65, 0x74, 0x63, 0x68,
0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48,
0x00, 0x52, 0x10, 0x70, 0x72, 0x65, 0x66, 0x65, 0x74, 0x63, 0x68, 0x43, 0x6f, 0x6e, 0x74, 0x65,
0x6e, 0x74, 0x73, 0x12, 0x65, 0x0a, 0x16, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x5f, 0x72, 0x65, 0x74,
0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x14, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x70, 0x6f,
0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x52, 0x65, 0x74, 0x65,
0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x48, 0x00, 0x52, 0x14, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x52, 0x65, 0x74, 0x65, 0x6e,
0x74, 0x69, 0x6f, 0x6e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x1a, 0x3f, 0x0a, 0x11, 0x54, 0x72,
0x61, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 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, 0x09, 0x0a, 0x07, 0x72,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x97, 0x08, 0x0a, 0x0f, 0x53, 0x65, 0x73, 0x73, 0x69,
0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09,
0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x68, 0x61, 0x73,
0x5f, 0x6d, 0x6f, 0x72, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x68, 0x61, 0x73,
0x4d, 0x6f, 0x72, 0x65, 0x12, 0x37, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x70, 0x6f,
0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x5c, 0x0a,
0x12, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x5f, 0x73, 0x65, 0x73, 0x73,
0x69, 0x6f, 0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x6b, 0x6f, 0x70, 0x69,
0x61, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x49, 0x6e, 0x69,
0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x11, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61,
0x6c, 0x69, 0x7a, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x54, 0x0a, 0x10, 0x67,
0x65, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18,
0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65,
0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x74,
0x65, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48,
0x00, 0x52, 0x0e, 0x67, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x66,
0x6f, 0x12, 0x37, 0x0a, 0x05, 0x66, 0x6c, 0x75, 0x73, 0x68, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b,
0x32, 0x1f, 0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74,
0x6f, 0x72, 0x79, 0x2e, 0x46, 0x6c, 0x75, 0x73, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x48, 0x00, 0x52, 0x05, 0x66, 0x6c, 0x75, 0x73, 0x68, 0x12, 0x4d, 0x0a, 0x0d, 0x77, 0x72,
0x69, 0x74, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x0d, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x26, 0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69,
0x74, 0x6f, 0x72, 0x79, 0x2e, 0x57, 0x72, 0x69, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e,
0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x0c, 0x77, 0x72, 0x69,
0x74, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x12, 0x47, 0x0a, 0x0b, 0x67, 0x65, 0x74,
0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24,
0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72,
0x79, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x0a, 0x67, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x65,
0x6e, 0x74, 0x12, 0x4a, 0x0a, 0x0c, 0x67, 0x65, 0x74, 0x5f, 0x6d, 0x61, 0x6e, 0x69, 0x66, 0x65,
0x73, 0x74, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61,
0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x47, 0x65, 0x74, 0x4d,
0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48,
0x00, 0x52, 0x0b, 0x67, 0x65, 0x74, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x12, 0x4a,
0x0a, 0x0c, 0x70, 0x75, 0x74, 0x5f, 0x6d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x18, 0x10,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x70,
0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x50, 0x75, 0x74, 0x4d, 0x61, 0x6e, 0x69, 0x66,
0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x0b, 0x70,
0x75, 0x74, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x12, 0x50, 0x0a, 0x0e, 0x66, 0x69,
0x6e, 0x64, 0x5f, 0x6d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x73, 0x18, 0x11, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73,
0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x46, 0x69, 0x6e, 0x64, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65,
0x73, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x0d, 0x66,
0x69, 0x6e, 0x64, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x73, 0x12, 0x53, 0x0a, 0x0f,
0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x5f, 0x6d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x18,
0x12, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65,
0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d,
0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48,
0x00, 0x52, 0x0e, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73,
0x74, 0x12, 0x59, 0x0a, 0x11, 0x70, 0x72, 0x65, 0x66, 0x65, 0x74, 0x63, 0x68, 0x5f, 0x63, 0x6f,
0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x13, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x6b,
0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e,
0x50, 0x72, 0x65, 0x66, 0x65, 0x74, 0x63, 0x68, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x10, 0x70, 0x72, 0x65, 0x66,
0x65, 0x74, 0x63, 0x68, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x66, 0x0a, 0x16,
0x61, 0x70, 0x70, 0x6c, 0x79, 0x5f, 0x72, 0x65, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x5f,
0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x6b,
0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e,
0x41, 0x70, 0x70, 0x6c, 0x79, 0x52, 0x65, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x6f,
0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x14,
0x61, 0x70, 0x70, 0x6c, 0x79, 0x52, 0x65, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x6f,
0x6c, 0x69, 0x63, 0x79, 0x42, 0x0a, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x32, 0x65, 0x0a, 0x0f, 0x4b, 0x6f, 0x70, 0x69, 0x61, 0x52, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74,
0x6f, 0x72, 0x79, 0x12, 0x52, 0x0a, 0x07, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x20,
0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72,
0x79, 0x2e, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x1a, 0x21, 0x2e, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74,
0x6f, 0x72, 0x79, 0x2e, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x28, 0x01, 0x30, 0x01, 0x42, 0x29, 0x5a, 0x27, 0x67, 0x69, 0x74, 0x68, 0x75,
0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6b, 0x6f, 0x70, 0x69, 0x61, 0x2f, 0x6b, 0x6f, 0x70, 0x69,
0x61, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x61,
0x70, 0x69, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
@@ -2100,50 +2253,52 @@ func file_repository_server_proto_rawDescGZIP() []byte {
}
var file_repository_server_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
var file_repository_server_proto_msgTypes = make([]protoimpl.MessageInfo, 30)
var file_repository_server_proto_msgTypes = make([]protoimpl.MessageInfo, 32)
var file_repository_server_proto_goTypes = []interface{}{
(ErrorResponse_Code)(0), // 0: kopia_repository.ErrorResponse.Code
(*ContentInfo)(nil), // 1: kopia_repository.ContentInfo
(*ManifestEntryMetadata)(nil), // 2: kopia_repository.ManifestEntryMetadata
(*ErrorResponse)(nil), // 3: kopia_repository.ErrorResponse
(*RepositoryParameters)(nil), // 4: kopia_repository.RepositoryParameters
(*InitializeSessionRequest)(nil), // 5: kopia_repository.InitializeSessionRequest
(*InitializeSessionResponse)(nil), // 6: kopia_repository.InitializeSessionResponse
(*GetContentInfoRequest)(nil), // 7: kopia_repository.GetContentInfoRequest
(*GetContentInfoResponse)(nil), // 8: kopia_repository.GetContentInfoResponse
(*GetContentRequest)(nil), // 9: kopia_repository.GetContentRequest
(*GetContentResponse)(nil), // 10: kopia_repository.GetContentResponse
(*FlushRequest)(nil), // 11: kopia_repository.FlushRequest
(*FlushResponse)(nil), // 12: kopia_repository.FlushResponse
(*WriteContentRequest)(nil), // 13: kopia_repository.WriteContentRequest
(*WriteContentResponse)(nil), // 14: kopia_repository.WriteContentResponse
(*GetManifestRequest)(nil), // 15: kopia_repository.GetManifestRequest
(*GetManifestResponse)(nil), // 16: kopia_repository.GetManifestResponse
(*PutManifestRequest)(nil), // 17: kopia_repository.PutManifestRequest
(*PutManifestResponse)(nil), // 18: kopia_repository.PutManifestResponse
(*DeleteManifestRequest)(nil), // 19: kopia_repository.DeleteManifestRequest
(*DeleteManifestResponse)(nil), // 20: kopia_repository.DeleteManifestResponse
(*FindManifestsRequest)(nil), // 21: kopia_repository.FindManifestsRequest
(*FindManifestsResponse)(nil), // 22: kopia_repository.FindManifestsResponse
(*PrefetchContentsRequest)(nil), // 23: kopia_repository.PrefetchContentsRequest
(*PrefetchContentsResponse)(nil), // 24: kopia_repository.PrefetchContentsResponse
(*SessionRequest)(nil), // 25: kopia_repository.SessionRequest
(*SessionResponse)(nil), // 26: kopia_repository.SessionResponse
nil, // 27: kopia_repository.ManifestEntryMetadata.LabelsEntry
nil, // 28: kopia_repository.PutManifestRequest.LabelsEntry
nil, // 29: kopia_repository.FindManifestsRequest.LabelsEntry
nil, // 30: kopia_repository.SessionRequest.TraceContextEntry
(ErrorResponse_Code)(0), // 0: kopia_repository.ErrorResponse.Code
(*ContentInfo)(nil), // 1: kopia_repository.ContentInfo
(*ManifestEntryMetadata)(nil), // 2: kopia_repository.ManifestEntryMetadata
(*ErrorResponse)(nil), // 3: kopia_repository.ErrorResponse
(*RepositoryParameters)(nil), // 4: kopia_repository.RepositoryParameters
(*InitializeSessionRequest)(nil), // 5: kopia_repository.InitializeSessionRequest
(*InitializeSessionResponse)(nil), // 6: kopia_repository.InitializeSessionResponse
(*GetContentInfoRequest)(nil), // 7: kopia_repository.GetContentInfoRequest
(*GetContentInfoResponse)(nil), // 8: kopia_repository.GetContentInfoResponse
(*GetContentRequest)(nil), // 9: kopia_repository.GetContentRequest
(*GetContentResponse)(nil), // 10: kopia_repository.GetContentResponse
(*FlushRequest)(nil), // 11: kopia_repository.FlushRequest
(*FlushResponse)(nil), // 12: kopia_repository.FlushResponse
(*WriteContentRequest)(nil), // 13: kopia_repository.WriteContentRequest
(*WriteContentResponse)(nil), // 14: kopia_repository.WriteContentResponse
(*GetManifestRequest)(nil), // 15: kopia_repository.GetManifestRequest
(*GetManifestResponse)(nil), // 16: kopia_repository.GetManifestResponse
(*PutManifestRequest)(nil), // 17: kopia_repository.PutManifestRequest
(*PutManifestResponse)(nil), // 18: kopia_repository.PutManifestResponse
(*DeleteManifestRequest)(nil), // 19: kopia_repository.DeleteManifestRequest
(*DeleteManifestResponse)(nil), // 20: kopia_repository.DeleteManifestResponse
(*FindManifestsRequest)(nil), // 21: kopia_repository.FindManifestsRequest
(*FindManifestsResponse)(nil), // 22: kopia_repository.FindManifestsResponse
(*PrefetchContentsRequest)(nil), // 23: kopia_repository.PrefetchContentsRequest
(*PrefetchContentsResponse)(nil), // 24: kopia_repository.PrefetchContentsResponse
(*ApplyRetentionPolicyRequest)(nil), // 25: kopia_repository.ApplyRetentionPolicyRequest
(*ApplyRetentionPolicyResponse)(nil), // 26: kopia_repository.ApplyRetentionPolicyResponse
(*SessionRequest)(nil), // 27: kopia_repository.SessionRequest
(*SessionResponse)(nil), // 28: kopia_repository.SessionResponse
nil, // 29: kopia_repository.ManifestEntryMetadata.LabelsEntry
nil, // 30: kopia_repository.PutManifestRequest.LabelsEntry
nil, // 31: kopia_repository.FindManifestsRequest.LabelsEntry
nil, // 32: kopia_repository.SessionRequest.TraceContextEntry
}
var file_repository_server_proto_depIdxs = []int32{
27, // 0: kopia_repository.ManifestEntryMetadata.labels:type_name -> kopia_repository.ManifestEntryMetadata.LabelsEntry
29, // 0: kopia_repository.ManifestEntryMetadata.labels:type_name -> kopia_repository.ManifestEntryMetadata.LabelsEntry
0, // 1: kopia_repository.ErrorResponse.code:type_name -> kopia_repository.ErrorResponse.Code
4, // 2: kopia_repository.InitializeSessionResponse.parameters:type_name -> kopia_repository.RepositoryParameters
1, // 3: kopia_repository.GetContentInfoResponse.info:type_name -> kopia_repository.ContentInfo
2, // 4: kopia_repository.GetManifestResponse.metadata:type_name -> kopia_repository.ManifestEntryMetadata
28, // 5: kopia_repository.PutManifestRequest.labels:type_name -> kopia_repository.PutManifestRequest.LabelsEntry
29, // 6: kopia_repository.FindManifestsRequest.labels:type_name -> kopia_repository.FindManifestsRequest.LabelsEntry
30, // 5: kopia_repository.PutManifestRequest.labels:type_name -> kopia_repository.PutManifestRequest.LabelsEntry
31, // 6: kopia_repository.FindManifestsRequest.labels:type_name -> kopia_repository.FindManifestsRequest.LabelsEntry
2, // 7: kopia_repository.FindManifestsResponse.metadata:type_name -> kopia_repository.ManifestEntryMetadata
30, // 8: kopia_repository.SessionRequest.trace_context:type_name -> kopia_repository.SessionRequest.TraceContextEntry
32, // 8: kopia_repository.SessionRequest.trace_context:type_name -> kopia_repository.SessionRequest.TraceContextEntry
5, // 9: kopia_repository.SessionRequest.initialize_session:type_name -> kopia_repository.InitializeSessionRequest
7, // 10: kopia_repository.SessionRequest.get_content_info:type_name -> kopia_repository.GetContentInfoRequest
11, // 11: kopia_repository.SessionRequest.flush:type_name -> kopia_repository.FlushRequest
@@ -2154,24 +2309,26 @@ func file_repository_server_proto_rawDescGZIP() []byte {
21, // 16: kopia_repository.SessionRequest.find_manifests:type_name -> kopia_repository.FindManifestsRequest
19, // 17: kopia_repository.SessionRequest.delete_manifest:type_name -> kopia_repository.DeleteManifestRequest
23, // 18: kopia_repository.SessionRequest.prefetch_contents:type_name -> kopia_repository.PrefetchContentsRequest
3, // 19: kopia_repository.SessionResponse.error:type_name -> kopia_repository.ErrorResponse
6, // 20: kopia_repository.SessionResponse.initialize_session:type_name -> kopia_repository.InitializeSessionResponse
8, // 21: kopia_repository.SessionResponse.get_content_info:type_name -> kopia_repository.GetContentInfoResponse
12, // 22: kopia_repository.SessionResponse.flush:type_name -> kopia_repository.FlushResponse
14, // 23: kopia_repository.SessionResponse.write_content:type_name -> kopia_repository.WriteContentResponse
10, // 24: kopia_repository.SessionResponse.get_content:type_name -> kopia_repository.GetContentResponse
16, // 25: kopia_repository.SessionResponse.get_manifest:type_name -> kopia_repository.GetManifestResponse
18, // 26: kopia_repository.SessionResponse.put_manifest:type_name -> kopia_repository.PutManifestResponse
22, // 27: kopia_repository.SessionResponse.find_manifests:type_name -> kopia_repository.FindManifestsResponse
20, // 28: kopia_repository.SessionResponse.delete_manifest:type_name -> kopia_repository.DeleteManifestResponse
24, // 29: kopia_repository.SessionResponse.prefetch_contents:type_name -> kopia_repository.PrefetchContentsResponse
25, // 30: kopia_repository.KopiaRepository.Session:input_type -> kopia_repository.SessionRequest
26, // 31: kopia_repository.KopiaRepository.Session:output_type -> kopia_repository.SessionResponse
31, // [31:32] is the sub-list for method output_type
30, // [30:31] is the sub-list for method input_type
30, // [30:30] is the sub-list for extension type_name
30, // [30:30] is the sub-list for extension extendee
0, // [0:30] is the sub-list for field type_name
25, // 19: kopia_repository.SessionRequest.apply_retention_policy:type_name -> kopia_repository.ApplyRetentionPolicyRequest
3, // 20: kopia_repository.SessionResponse.error:type_name -> kopia_repository.ErrorResponse
6, // 21: kopia_repository.SessionResponse.initialize_session:type_name -> kopia_repository.InitializeSessionResponse
8, // 22: kopia_repository.SessionResponse.get_content_info:type_name -> kopia_repository.GetContentInfoResponse
12, // 23: kopia_repository.SessionResponse.flush:type_name -> kopia_repository.FlushResponse
14, // 24: kopia_repository.SessionResponse.write_content:type_name -> kopia_repository.WriteContentResponse
10, // 25: kopia_repository.SessionResponse.get_content:type_name -> kopia_repository.GetContentResponse
16, // 26: kopia_repository.SessionResponse.get_manifest:type_name -> kopia_repository.GetManifestResponse
18, // 27: kopia_repository.SessionResponse.put_manifest:type_name -> kopia_repository.PutManifestResponse
22, // 28: kopia_repository.SessionResponse.find_manifests:type_name -> kopia_repository.FindManifestsResponse
20, // 29: kopia_repository.SessionResponse.delete_manifest:type_name -> kopia_repository.DeleteManifestResponse
24, // 30: kopia_repository.SessionResponse.prefetch_contents:type_name -> kopia_repository.PrefetchContentsResponse
26, // 31: kopia_repository.SessionResponse.apply_retention_policy:type_name -> kopia_repository.ApplyRetentionPolicyResponse
27, // 32: kopia_repository.KopiaRepository.Session:input_type -> kopia_repository.SessionRequest
28, // 33: kopia_repository.KopiaRepository.Session:output_type -> kopia_repository.SessionResponse
33, // [33:34] is the sub-list for method output_type
32, // [32:33] is the sub-list for method input_type
32, // [32:32] is the sub-list for extension type_name
32, // [32:32] is the sub-list for extension extendee
0, // [0:32] is the sub-list for field type_name
}
func init() { file_repository_server_proto_init() }
@@ -2469,7 +2626,7 @@ func file_repository_server_proto_init() {
}
}
file_repository_server_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SessionRequest); i {
switch v := v.(*ApplyRetentionPolicyRequest); i {
case 0:
return &v.state
case 1:
@@ -2481,6 +2638,30 @@ func file_repository_server_proto_init() {
}
}
file_repository_server_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ApplyRetentionPolicyResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_repository_server_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SessionRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_repository_server_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SessionResponse); i {
case 0:
return &v.state
@@ -2493,7 +2674,7 @@ func file_repository_server_proto_init() {
}
}
}
file_repository_server_proto_msgTypes[24].OneofWrappers = []interface{}{
file_repository_server_proto_msgTypes[26].OneofWrappers = []interface{}{
(*SessionRequest_InitializeSession)(nil),
(*SessionRequest_GetContentInfo)(nil),
(*SessionRequest_Flush)(nil),
@@ -2504,8 +2685,9 @@ func file_repository_server_proto_init() {
(*SessionRequest_FindManifests)(nil),
(*SessionRequest_DeleteManifest)(nil),
(*SessionRequest_PrefetchContents)(nil),
(*SessionRequest_ApplyRetentionPolicy)(nil),
}
file_repository_server_proto_msgTypes[25].OneofWrappers = []interface{}{
file_repository_server_proto_msgTypes[27].OneofWrappers = []interface{}{
(*SessionResponse_Error)(nil),
(*SessionResponse_InitializeSession)(nil),
(*SessionResponse_GetContentInfo)(nil),
@@ -2517,6 +2699,7 @@ func file_repository_server_proto_init() {
(*SessionResponse_FindManifests)(nil),
(*SessionResponse_DeleteManifest)(nil),
(*SessionResponse_PrefetchContents)(nil),
(*SessionResponse_ApplyRetentionPolicy)(nil),
}
type x struct{}
out := protoimpl.TypeBuilder{
@@ -2524,7 +2707,7 @@ type x struct{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_repository_server_proto_rawDesc,
NumEnums: 1,
NumMessages: 30,
NumMessages: 32,
NumExtensions: 0,
NumServices: 1,
},

View File

@@ -136,6 +136,15 @@ message PrefetchContentsResponse {
repeated string content_ids = 1;
}
message ApplyRetentionPolicyRequest {
string source_path = 1;
bool really_delete = 2;
}
message ApplyRetentionPolicyResponse {
repeated string manifest_ids = 1;
}
message SessionRequest {
int64 request_id = 1;
map<string,string> trace_context = 2;
@@ -154,6 +163,7 @@ message SessionRequest {
FindManifestsRequest find_manifests = 17;
DeleteManifestRequest delete_manifest = 18;
PrefetchContentsRequest prefetch_contents = 19;
ApplyRetentionPolicyRequest apply_retention_policy = 20;
}
}
@@ -174,6 +184,7 @@ message SessionResponse {
FindManifestsResponse find_manifests = 17;
DeleteManifestResponse delete_manifest = 18;
PrefetchContentsResponse prefetch_contents = 19;
ApplyRetentionPolicyResponse apply_retention_policy = 20;
}
}

View File

@@ -37,7 +37,18 @@ type PrefetchContentsRequest struct {
Hint string `json:"hint"`
}
// PrefetchContentsResponse represents a request from request to prefetch contents.
// PrefetchContentsResponse represents a response from request to prefetch contents.
type PrefetchContentsResponse struct {
ContentIDs []content.ID `json:"contents"`
}
// ApplyRetentionPolicyRequest represents a request to apply retention policy to a given source path.
type ApplyRetentionPolicyRequest struct {
SourcePath string `json:"sourcePath"`
ReallyDelete bool `json:"reallyDelete"`
}
// ApplyRetentionPolicyResponse represents a response to a request to apply retention policy.
type ApplyRetentionPolicyResponse struct {
ManifestIDs []manifest.ID `json:"manifests"`
}

View File

@@ -3,6 +3,7 @@
import (
"context"
"encoding/json"
"strings"
"github.com/pkg/errors"
@@ -11,6 +12,8 @@
"github.com/kopia/kopia/internal/serverapi"
"github.com/kopia/kopia/repo"
"github.com/kopia/kopia/repo/manifest"
"github.com/kopia/kopia/snapshot"
"github.com/kopia/kopia/snapshot/policy"
)
func handleManifestGet(ctx context.Context, rc requestContext) (interface{}, *apiError) {
@@ -123,3 +126,47 @@ func handleManifestCreate(ctx context.Context, rc requestContext) (interface{},
return &manifest.EntryMetadata{ID: id}, nil
}
func handleApplyRetentionPolicy(ctx context.Context, rc requestContext) (interface{}, *apiError) {
rw, ok := rc.rep.(repo.RepositoryWriter)
if !ok {
return nil, repositoryNotWritableError()
}
var req remoterepoapi.ApplyRetentionPolicyRequest
if err := json.Unmarshal(rc.body, &req); err != nil {
return nil, requestError(serverapi.ErrorMalformedRequest, "malformed request")
}
usernameAtHostname, _, _ := rc.req.BasicAuth()
parts := strings.Split(usernameAtHostname, "@")
if len(parts) != 2 { //nolint:gomnd
return nil, requestError(serverapi.ErrorMalformedRequest, "malformed username")
}
// only allow users to apply retention policy if they have permission to add snapshots
// for a particular path.
if !hasManifestAccess(ctx, rc, map[string]string{
manifest.TypeLabelKey: snapshot.ManifestType,
snapshot.UsernameLabel: parts[0],
snapshot.HostnameLabel: parts[1],
snapshot.PathLabel: req.SourcePath,
}, auth.AccessLevelAppend) {
return nil, accessDeniedError()
}
ids, err := policy.ApplyRetentionPolicy(ctx, rw, snapshot.SourceInfo{
UserName: parts[0],
Host: parts[1],
Path: req.SourcePath,
}, req.ReallyDelete)
if err != nil {
return nil, internalServerError(err)
}
return &remoterepoapi.ApplyRetentionPolicyResponse{
ManifestIDs: ids,
}, nil
}

View File

@@ -26,6 +26,8 @@
"github.com/kopia/kopia/repo/content"
"github.com/kopia/kopia/repo/manifest"
"github.com/kopia/kopia/repo/object"
"github.com/kopia/kopia/snapshot"
"github.com/kopia/kopia/snapshot/policy"
)
type grpcServerState struct {
@@ -82,12 +84,12 @@ func (s *Server) Session(srv grpcapi.KopiaRepository_SessionServer) error {
return status.Errorf(codes.Unavailable, "not connected to a direct repository")
}
username, err := s.authenticateGRPCSession(ctx, dr)
usernameAtHostname, err := s.authenticateGRPCSession(ctx, dr)
if err != nil {
return err
}
authz := s.authorizer.Authorize(ctx, dr, username)
authz := s.authorizer.Authorize(ctx, dr, usernameAtHostname)
if authz == nil {
authz = auth.NoAccess()
}
@@ -97,8 +99,8 @@ func (s *Server) Session(srv grpcapi.KopiaRepository_SessionServer) error {
return status.Errorf(codes.PermissionDenied, "peer not found in context")
}
log(ctx).Infof("starting session for user %q from %v", username, p.Addr)
defer log(ctx).Infof("session ended for user %q from %v", username, p.Addr)
log(ctx).Infof("starting session for user %q from %v", usernameAtHostname, p.Addr)
defer log(ctx).Infof("session ended for user %q from %v", usernameAtHostname, p.Addr)
opt, err := s.handleInitialSessionHandshake(srv, dr)
if err != nil {
@@ -131,7 +133,7 @@ func (s *Server) Session(srv grpcapi.KopiaRepository_SessionServer) error {
go func() {
defer s.grpcServerState.sem.Release(1)
handleSessionRequest(ctx, dw, authz, req, func(resp *grpcapi.SessionResponse) {
handleSessionRequest(ctx, dw, authz, usernameAtHostname, req, func(resp *grpcapi.SessionResponse) {
if err := s.send(srv, req.RequestId, resp); err != nil {
select {
case lastErr <- err:
@@ -148,7 +150,7 @@ func (s *Server) Session(srv grpcapi.KopiaRepository_SessionServer) error {
var tracer = otel.Tracer("kopia/grpc")
func handleSessionRequest(ctx context.Context, dw repo.DirectRepositoryWriter, authz auth.AuthorizationInfo, req *grpcapi.SessionRequest, respond func(*grpcapi.SessionResponse)) {
func handleSessionRequest(ctx context.Context, dw repo.DirectRepositoryWriter, authz auth.AuthorizationInfo, usernameAtHostname string, req *grpcapi.SessionRequest, respond func(*grpcapi.SessionResponse)) {
if req.TraceContext != nil {
var tc propagation.TraceContext
ctx = tc.Extract(ctx, propagation.MapCarrier(req.TraceContext))
@@ -182,6 +184,9 @@ func handleSessionRequest(ctx context.Context, dw repo.DirectRepositoryWriter, a
case *grpcapi.SessionRequest_PrefetchContents:
respond(handlePrefetchContentsRequest(ctx, dw, authz, inner.PrefetchContents))
case *grpcapi.SessionRequest_ApplyRetentionPolicy:
respond(handleApplyRetentionPolicyRequest(ctx, dw, authz, usernameAtHostname, inner.ApplyRetentionPolicy))
case *grpcapi.SessionRequest_InitializeSession:
respond(errorResponse(errors.Errorf("InitializeSession must be the first request in a session")))
@@ -440,6 +445,47 @@ func handlePrefetchContentsRequest(ctx context.Context, rep repo.Repository, aut
}
}
func handleApplyRetentionPolicyRequest(ctx context.Context, rep repo.RepositoryWriter, authz auth.AuthorizationInfo, usernameAtHostname string, req *grpcapi.ApplyRetentionPolicyRequest) *grpcapi.SessionResponse {
ctx, span := tracer.Start(ctx, "GRPCSession.ApplyRetentionPolicy")
defer span.End()
parts := strings.Split(usernameAtHostname, "@")
if len(parts) != 2 { //nolint:gomnd
return errorResponse(errors.Errorf("invalid username@hostname: %q", usernameAtHostname))
}
username := parts[0]
hostname := parts[1]
// only allow users to apply retention policy if they have permission to add snapshots
// for a particular path.
if authz.ManifestAccessLevel(map[string]string{
manifest.TypeLabelKey: snapshot.ManifestType,
snapshot.UsernameLabel: username,
snapshot.HostnameLabel: hostname,
snapshot.PathLabel: req.SourcePath,
}) < auth.AccessLevelAppend {
return accessDeniedResponse()
}
manifestIDs, err := policy.ApplyRetentionPolicy(ctx, rep, snapshot.SourceInfo{
Host: hostname,
UserName: username,
Path: req.SourcePath,
}, req.ReallyDelete)
if err != nil {
return errorResponse(err)
}
return &grpcapi.SessionResponse{
Response: &grpcapi.SessionResponse_ApplyRetentionPolicy{
ApplyRetentionPolicy: &grpcapi.ApplyRetentionPolicyResponse{
ManifestIds: manifest.IDsToStrings(manifestIDs),
},
},
}
}
func accessDeniedResponse() *grpcapi.SessionResponse {
return &grpcapi.SessionResponse{
Response: &grpcapi.SessionResponse_Error{

View File

@@ -163,6 +163,7 @@ func (s *Server) SetupRepositoryAPIHandlers(m *mux.Router) {
m.HandleFunc("/api/v1/manifests/{manifestID}", s.handleRepositoryAPI(handlerWillCheckAuthorization, handleManifestDelete)).Methods(http.MethodDelete)
m.HandleFunc("/api/v1/manifests", s.handleRepositoryAPI(handlerWillCheckAuthorization, handleManifestCreate)).Methods(http.MethodPost)
m.HandleFunc("/api/v1/manifests", s.handleRepositoryAPI(handlerWillCheckAuthorization, handleManifestList)).Methods(http.MethodGet)
m.HandleFunc("/api/v1/policies/apply-retention", s.handleRepositoryAPI(handlerWillCheckAuthorization, handleApplyRetentionPolicy)).Methods(http.MethodPost)
}
// SetupControlAPIHandlers registers control API handlers.

View File

@@ -282,6 +282,19 @@ func (r *apiServerRepository) PrefetchContents(ctx context.Context, contentIDs [
return resp.ContentIDs
}
func (r *apiServerRepository) ApplyRetentionPolicy(ctx context.Context, sourcePath string, reallyDelete bool) ([]manifest.ID, error) {
var result remoterepoapi.ApplyRetentionPolicyResponse
if err := r.cli.Post(ctx, "policies/apply-retention", remoterepoapi.ApplyRetentionPolicyRequest{
SourcePath: sourcePath,
ReallyDelete: reallyDelete,
}, &result); err != nil {
return nil, errors.Wrap(err, "unable to apply retention policy")
}
return result.ManifestIDs, nil
}
// OnSuccessfulFlush registers the provided callback to be invoked after flush succeeds.
func (r *apiServerRepository) OnSuccessfulFlush(callback RepositoryWriterCallback) {
r.afterFlush = append(r.afterFlush, callback)

View File

@@ -441,6 +441,33 @@ func (r *grpcInnerSession) PrefetchContents(ctx context.Context, contentIDs []co
return nil
}
func (r *grpcRepositoryClient) ApplyRetentionPolicy(ctx context.Context, sourcePath string, reallyDelete bool) ([]manifest.ID, error) {
return inSessionWithoutRetry(ctx, r, func(ctx context.Context, sess *grpcInnerSession) ([]manifest.ID, error) {
return sess.ApplyRetentionPolicy(ctx, sourcePath, reallyDelete)
})
}
func (r *grpcInnerSession) ApplyRetentionPolicy(ctx context.Context, sourcePath string, reallyDelete bool) ([]manifest.ID, error) {
for resp := range r.sendRequest(ctx, &apipb.SessionRequest{
Request: &apipb.SessionRequest_ApplyRetentionPolicy{
ApplyRetentionPolicy: &apipb.ApplyRetentionPolicyRequest{
SourcePath: sourcePath,
ReallyDelete: reallyDelete,
},
},
}) {
switch rr := resp.Response.(type) {
case *apipb.SessionResponse_ApplyRetentionPolicy:
return manifest.IDsFromStrings(rr.ApplyRetentionPolicy.ManifestIds), nil
default:
return nil, unhandledSessionResponse(resp)
}
}
return nil, errNoSessionResponse()
}
func (r *grpcRepositoryClient) Time() time.Time {
return clock.Now()
}

View File

@@ -253,6 +253,28 @@ func (m *Manager) Compact(ctx context.Context) error {
return m.committed.compact(ctx)
}
// IDsToStrings converts the IDs to strings.
func IDsToStrings(input []ID) []string {
var result []string
for _, v := range input {
result = append(result, string(v))
}
return result
}
// IDsFromStrings converts the IDs to strings.
func IDsFromStrings(input []string) []ID {
var result []ID
for _, v := range input {
result = append(result, ID(v))
}
return result
}
func copyLabels(m map[string]string) map[string]string {
r := map[string]string{}
for k, v := range m {

View File

@@ -54,6 +54,12 @@ type RepositoryWriter interface {
Flush(ctx context.Context) error
}
// RemoteRetentionPolicy is an interface implemented by repository clients that support remote retention policy.
// when implemented, the repository server will invoke ApplyRetentionPolicy() server-side.
type RemoteRetentionPolicy interface {
ApplyRetentionPolicy(ctx context.Context, sourcePath string, reallyDelete bool) ([]manifest.ID, error)
}
// DirectRepository provides additional low-level repository functionality.
//
//nolint:interfacebloat

View File

@@ -7,11 +7,24 @@
"github.com/pkg/errors"
"github.com/kopia/kopia/repo"
"github.com/kopia/kopia/repo/manifest"
"github.com/kopia/kopia/snapshot"
)
// ApplyRetentionPolicy applies retention policy to a given source by deleting expired snapshots.
func ApplyRetentionPolicy(ctx context.Context, rep repo.RepositoryWriter, sourceInfo snapshot.SourceInfo, reallyDelete bool) ([]*snapshot.Manifest, error) {
func ApplyRetentionPolicy(ctx context.Context, rep repo.RepositoryWriter, sourceInfo snapshot.SourceInfo, reallyDelete bool) ([]manifest.ID, error) {
// it is desired to not allow snapshots to be deleted by repository clients,
// while still maintain the ability to apply snapshot retention policies server-side.
if remote, ok := rep.(repo.RemoteRetentionPolicy); ok {
if sourceInfo.UserName == rep.ClientOptions().Username && sourceInfo.Host == rep.ClientOptions().Hostname {
// for repository clients, apply retention policy on the server.
log(ctx).Debug("applying retention policy on the server")
//nolint:wrapcheck
return remote.ApplyRetentionPolicy(ctx, sourceInfo.Path, reallyDelete)
}
}
snapshots, err := snapshot.ListSnapshots(ctx, rep, sourceInfo)
if err != nil {
return nil, errors.Wrap(err, "error listing snapshots")
@@ -23,9 +36,9 @@ func ApplyRetentionPolicy(ctx context.Context, rep repo.RepositoryWriter, source
}
if reallyDelete {
for _, it := range toDelete {
if err := rep.DeleteManifest(ctx, it.ID); err != nil {
return toDelete, errors.Wrapf(err, "error deleting manifest %v", it.ID)
for _, manifestID := range toDelete {
if err := rep.DeleteManifest(ctx, manifestID); err != nil {
return toDelete, errors.Wrapf(err, "error deleting manifest %v", manifestID)
}
}
}
@@ -33,8 +46,8 @@ func ApplyRetentionPolicy(ctx context.Context, rep repo.RepositoryWriter, source
return toDelete, nil
}
func getExpiredSnapshots(ctx context.Context, rep repo.Repository, snapshots []*snapshot.Manifest) ([]*snapshot.Manifest, error) {
var toDelete []*snapshot.Manifest
func getExpiredSnapshots(ctx context.Context, rep repo.Repository, snapshots []*snapshot.Manifest) ([]manifest.ID, error) {
var toDelete []manifest.ID
for _, snapshotGroup := range snapshot.GroupBySource(snapshots) {
td, err := getExpiredSnapshotsForSource(ctx, rep, snapshotGroup)
@@ -48,7 +61,7 @@ func getExpiredSnapshots(ctx context.Context, rep repo.Repository, snapshots []*
return toDelete, nil
}
func getExpiredSnapshotsForSource(ctx context.Context, rep repo.Repository, snapshots []*snapshot.Manifest) ([]*snapshot.Manifest, error) {
func getExpiredSnapshotsForSource(ctx context.Context, rep repo.Repository, snapshots []*snapshot.Manifest) ([]manifest.ID, error) {
src := snapshots[0].Source
pol, _, _, err := GetEffectivePolicy(ctx, rep, src)
@@ -58,12 +71,12 @@ func getExpiredSnapshotsForSource(ctx context.Context, rep repo.Repository, snap
pol.RetentionPolicy.ComputeRetentionReasons(snapshots)
var toDelete []*snapshot.Manifest
var toDelete []manifest.ID
for _, s := range snapshots {
if len(s.RetentionReasons) == 0 && len(s.Pins) == 0 {
log(ctx).Debugf(" deleting %v", s.StartTime)
toDelete = append(toDelete, s)
toDelete = append(toDelete, s.ID)
} else {
log(ctx).Debugf(" keeping %v retention: [%v] pins: [%v]", s.StartTime, strings.Join(s.RetentionReasons, ","), strings.Join(s.Pins, ","))
}

View File

@@ -1,17 +1,34 @@
package endtoend_test
import (
"fmt"
"testing"
"github.com/stretchr/testify/require"
"github.com/kopia/kopia/internal/auth"
"github.com/kopia/kopia/internal/testutil"
"github.com/kopia/kopia/tests/clitestutil"
"github.com/kopia/kopia/tests/testenv"
)
func TestACL(t *testing.T) {
func TestACL_GRPC(t *testing.T) {
verifyACL(t, false)
}
func TestACL_HTTP(t *testing.T) {
verifyACL(t, true)
}
//nolint:thelper
func verifyACL(t *testing.T, disableGRPC bool) {
t.Parallel()
grpcArgument := "--grpc"
if disableGRPC {
grpcArgument = "--no-grpc"
}
serverRunner := testenv.NewInProcRunner(t)
serverEnvironment := testenv.NewCLITest(t, testenv.RepoFormatNotImportant, serverRunner)
@@ -19,27 +36,38 @@ func TestACL(t *testing.T) {
serverEnvironment.RunAndExpectSuccess(t, "repo", "create", "filesystem", "--path", serverEnvironment.RepoDir, "--override-hostname=foo", "--override-username=foo", "--enable-actions")
if got, want := len(serverEnvironment.RunAndExpectSuccess(t, "server", "acl", "list")), 0; got != want {
t.Fatalf("unexpected ACLs found")
}
require.Len(t, serverEnvironment.RunAndExpectSuccess(t, "server", "acl", "list"), 0)
// enable ACLs - that should insert all the rules.
serverEnvironment.RunAndExpectSuccess(t, "server", "acl", "enable")
if got, want := len(serverEnvironment.RunAndExpectSuccess(t, "server", "acl", "list")), len(auth.DefaultACLs); got != want {
t.Fatalf("unexpected ACLs found")
}
require.Len(t, serverEnvironment.RunAndExpectSuccess(t, "server", "acl", "list"), len(auth.DefaultACLs))
// reduce default access to snapshots to APPEND - this will fail because exactly identical rule already exists and grants FULL access.
serverEnvironment.RunAndExpectFailure(t, "server", "acl", "add", "--user", "*@*", "--target", "type=snapshot,username=OWN_USER,hostname=OWN_HOST", "--access=APPEND")
// reduce default access to snapshots to APPEND with --overwrite, this wil succeed.
serverEnvironment.RunAndExpectSuccess(t, "server", "acl", "add", "--user", "*@*", "--target", "type=snapshot,username=OWN_USER,hostname=OWN_HOST", "--access=APPEND", "--overwrite")
// add read access to all snapshots and policies for user foo@bar
serverEnvironment.RunAndExpectSuccess(t, "server", "acl", "add", "--user", "foo@bar", "--target", "type=snapshot", "--access=READ")
serverEnvironment.RunAndExpectSuccess(t, "server", "acl", "add", "--user", "foo@bar", "--target", "type=policy", "--access=READ")
// add append access to all snapshots and read-only access to policies for user another@bar
serverEnvironment.RunAndExpectSuccess(t, "server", "acl", "add", "--user", "another@bar", "--target", "type=snapshot", "--access=APPEND")
serverEnvironment.RunAndExpectSuccess(t, "server", "acl", "add", "--user", "another@bar", "--target", "type=policy", "--access=READ")
// add full access to global policy for all users
serverEnvironment.RunAndExpectSuccess(t, "server", "acl", "add", "--user", "*@*", "--target", "type=policy,policyType=global", "--access=FULL")
serverEnvironment.RunAndExpectSuccess(t, "server", "users", "add", "foo@bar", "--user-password", "baz")
serverEnvironment.RunAndExpectSuccess(t, "server", "users", "add", "another@bar", "--user-password", "baz")
serverEnvironment.RunAndExpectSuccess(t, "server", "users", "add", "alice@wonderland", "--user-password", "baz")
const keepLatestSnapshots = 3
serverEnvironment.RunAndExpectSuccess(t, "policy", "set", "another@bar", fmt.Sprintf("--keep-latest=%v", keepLatestSnapshots))
var sp testutil.ServerParameters
wait, kill := serverEnvironment.RunAndProcessStderr(t, sp.ProcessOutput,
@@ -70,6 +98,24 @@ func TestACL(t *testing.T) {
"--override-username", "foo",
"--override-hostname", "bar",
"--password", "baz",
grpcArgument,
)
anotherBarRunner := testenv.NewInProcRunner(t)
anotherBarClientEnvironment := testenv.NewCLITest(t, testenv.RepoFormatNotImportant, anotherBarRunner)
defer anotherBarClientEnvironment.RunAndExpectSuccess(t, "repo", "disconnect")
delete(anotherBarClientEnvironment.Environment, "KOPIA_PASSWORD")
// connect as foo@bar with password baz
anotherBarClientEnvironment.RunAndExpectSuccess(t, "repo", "connect", "server",
"--url", sp.BaseURL+"/",
"--server-cert-fingerprint", sp.SHA256Fingerprint,
"--override-username", "another",
"--override-hostname", "bar",
"--password", "baz",
grpcArgument,
)
aliceInWonderlandRunner := testenv.NewInProcRunner(t)
@@ -86,6 +132,7 @@ func TestACL(t *testing.T) {
"--override-username", "alice",
"--override-hostname", "wonderland",
"--password", "baz",
grpcArgument,
)
// both alice and foo@bar can see global policy
@@ -117,6 +164,23 @@ func TestACL(t *testing.T) {
t.Fatalf("foo@bar expected to see 1 source (own), got %v", snaps)
}
// another@bar can create snapshots but not delete them
anotherBarClientEnvironment.RunAndExpectSuccess(t, "snapshot", "create", sharedTestDataDir1)
anotherBarClientEnvironment.RunAndExpectSuccess(t, "snapshot", "create", sharedTestDataDir1)
anotherBarClientEnvironment.RunAndExpectSuccess(t, "snapshot", "create", sharedTestDataDir1)
anotherBarClientEnvironment.RunAndExpectSuccess(t, "snapshot", "create", sharedTestDataDir1)
anotherBarClientEnvironment.RunAndExpectSuccess(t, "snapshot", "create", sharedTestDataDir1)
anotherBarClientEnvironment.RunAndExpectSuccess(t, "snapshot", "create", sharedTestDataDir1)
// make sure only `keepLatestSnapshots` snapshots are kept, so retention policy
// is working.
snapshots := clitestutil.ListSnapshotsAndExpectSuccess(t, anotherBarClientEnvironment, sharedTestDataDir1)[0].Snapshots
require.Len(t, snapshots, keepLatestSnapshots)
// APPEND policy despite being able to maintain retention rules, prevents snapshots from being deleted
// by the client.
anotherBarClientEnvironment.RunAndExpectFailure(t, "snapshot", "delete", snapshots[0].SnapshotID, "--delete")
// alice changes her own password and reconnects
aliceInWonderlandClientEnvironment.RunAndExpectSuccess(t, "server", "users", "set", "alice@wonderland", "--user-password", "new-password")