From 049363b882f346ebcae8a0b25ed9e2fc45898690 Mon Sep 17 00:00:00 2001 From: Michael Wilson Date: Tue, 3 Oct 2023 18:36:05 -0400 Subject: [PATCH] Add in audit review recurrence presets. (#32843) Access Lists now have audit review recurrence presets. These allow users to specify review frequencies of 1, 3, 6, or 12 months, and specify the 1st, 15th, or last days of the target month. Presets have been used for their simplicity over other various recurrence definition mechanisms, as these presets are much clearer than many of the other options. --- api/client/events_test.go | 11 +- .../teleport/accesslist/v1/accesslist.pb.go | 633 ++++++++++++------ .../teleport/accesslist/v1/accesslist.proto | 44 +- api/types/accesslist/accesslist.go | 163 ++++- api/types/accesslist/accesslist_test.go | 102 ++- api/types/accesslist/convert/v1/accesslist.go | 14 +- .../accesslist/convert/v1/accesslist_test.go | 1 - api/types/accesslist/convert/v1/review.go | 25 +- .../accesslist/convert/v1/review_test.go | 69 +- api/types/accesslist/review.go | 28 +- api/types/accesslist/review_test.go | 16 +- lib/auth/userloginstate/generator_test.go | 1 - lib/cache/cache_test.go | 14 +- lib/services/access_list.go | 20 + lib/services/access_list_test.go | 58 +- lib/services/local/access_list_test.go | 18 +- lib/services/unified_resource_test.go | 2 +- tool/tctl/common/acl_command.go | 5 +- 18 files changed, 913 insertions(+), 311 deletions(-) diff --git a/api/client/events_test.go b/api/client/events_test.go index 7ebed689242f..79dc6907e75a 100644 --- a/api/client/events_test.go +++ b/api/client/events_test.go @@ -16,8 +16,8 @@ package client import ( "testing" - "time" + "github.com/jonboulle/clockwork" "github.com/stretchr/testify/require" "google.golang.org/protobuf/proto" @@ -32,6 +32,7 @@ import ( // primarily to catch potential issues with using our "mixed" gogo + regular protobuf // strategy. func TestEventEqual(t *testing.T) { + clock := clockwork.NewFakeClock() app1, err := types.NewAppV3(types.Metadata{ Name: "app1", }, types.AppSpecV3{ @@ -56,8 +57,8 @@ func TestEventEqual(t *testing.T) { }) require.NoError(t, err) - accessList1 := newAccessList(t, "1") - accessList2 := newAccessList(t, "2") + accessList1 := newAccessList(t, "1", clock) + accessList2 := newAccessList(t, "2", clock) tests := []struct { name string @@ -158,7 +159,7 @@ func TestEventEqual(t *testing.T) { } } -func newAccessList(t *testing.T, name string) *accesslist.AccessList { +func newAccessList(t *testing.T, name string, clock clockwork.Clock) *accesslist.AccessList { t.Helper() accessList, err := accesslist.NewAccessList( @@ -179,7 +180,7 @@ func newAccessList(t *testing.T, name string) *accesslist.AccessList { }, }, Audit: accesslist.Audit{ - Frequency: time.Hour, + NextAuditDate: clock.Now(), }, MembershipRequires: accesslist.Requires{ Roles: []string{"mrole1", "mrole2"}, diff --git a/api/gen/proto/go/teleport/accesslist/v1/accesslist.pb.go b/api/gen/proto/go/teleport/accesslist/v1/accesslist.pb.go index e0326a9cfb13..2b4c65cac6ab 100644 --- a/api/gen/proto/go/teleport/accesslist/v1/accesslist.pb.go +++ b/api/gen/proto/go/teleport/accesslist/v1/accesslist.pb.go @@ -25,7 +25,6 @@ import ( v11 "github.com/gravitational/teleport/api/gen/proto/go/teleport/trait/v1" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" - durationpb "google.golang.org/protobuf/types/known/durationpb" timestamppb "google.golang.org/protobuf/types/known/timestamppb" reflect "reflect" sync "sync" @@ -38,6 +37,115 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) +// ReviewFrequency is the frequency of reviews. +type ReviewFrequency int32 + +const ( + ReviewFrequency_REVIEW_FREQUENCY_UNSPECIFIED ReviewFrequency = 0 + ReviewFrequency_REVIEW_FREQUENCY_ONE_MONTH ReviewFrequency = 1 + ReviewFrequency_REVIEW_FREQUENCY_THREE_MONTHS ReviewFrequency = 3 + ReviewFrequency_REVIEW_FREQUENCY_SIX_MONTHS ReviewFrequency = 6 + ReviewFrequency_REVIEW_FREQUENCY_ONE_YEAR ReviewFrequency = 12 +) + +// Enum value maps for ReviewFrequency. +var ( + ReviewFrequency_name = map[int32]string{ + 0: "REVIEW_FREQUENCY_UNSPECIFIED", + 1: "REVIEW_FREQUENCY_ONE_MONTH", + 3: "REVIEW_FREQUENCY_THREE_MONTHS", + 6: "REVIEW_FREQUENCY_SIX_MONTHS", + 12: "REVIEW_FREQUENCY_ONE_YEAR", + } + ReviewFrequency_value = map[string]int32{ + "REVIEW_FREQUENCY_UNSPECIFIED": 0, + "REVIEW_FREQUENCY_ONE_MONTH": 1, + "REVIEW_FREQUENCY_THREE_MONTHS": 3, + "REVIEW_FREQUENCY_SIX_MONTHS": 6, + "REVIEW_FREQUENCY_ONE_YEAR": 12, + } +) + +func (x ReviewFrequency) Enum() *ReviewFrequency { + p := new(ReviewFrequency) + *p = x + return p +} + +func (x ReviewFrequency) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ReviewFrequency) Descriptor() protoreflect.EnumDescriptor { + return file_teleport_accesslist_v1_accesslist_proto_enumTypes[0].Descriptor() +} + +func (ReviewFrequency) Type() protoreflect.EnumType { + return &file_teleport_accesslist_v1_accesslist_proto_enumTypes[0] +} + +func (x ReviewFrequency) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ReviewFrequency.Descriptor instead. +func (ReviewFrequency) EnumDescriptor() ([]byte, []int) { + return file_teleport_accesslist_v1_accesslist_proto_rawDescGZIP(), []int{0} +} + +// ReviewDayOfMonth is the day of month that reviews will repeat on. +type ReviewDayOfMonth int32 + +const ( + ReviewDayOfMonth_REVIEW_DAY_OF_MONTH_UNSPECIFIED ReviewDayOfMonth = 0 + ReviewDayOfMonth_REVIEW_DAY_OF_MONTH_FIRST ReviewDayOfMonth = 1 + ReviewDayOfMonth_REVIEW_DAY_OF_MONTH_FIFTEENTH ReviewDayOfMonth = 15 + ReviewDayOfMonth_REVIEW_DAY_OF_MONTH_LAST ReviewDayOfMonth = 31 +) + +// Enum value maps for ReviewDayOfMonth. +var ( + ReviewDayOfMonth_name = map[int32]string{ + 0: "REVIEW_DAY_OF_MONTH_UNSPECIFIED", + 1: "REVIEW_DAY_OF_MONTH_FIRST", + 15: "REVIEW_DAY_OF_MONTH_FIFTEENTH", + 31: "REVIEW_DAY_OF_MONTH_LAST", + } + ReviewDayOfMonth_value = map[string]int32{ + "REVIEW_DAY_OF_MONTH_UNSPECIFIED": 0, + "REVIEW_DAY_OF_MONTH_FIRST": 1, + "REVIEW_DAY_OF_MONTH_FIFTEENTH": 15, + "REVIEW_DAY_OF_MONTH_LAST": 31, + } +) + +func (x ReviewDayOfMonth) Enum() *ReviewDayOfMonth { + p := new(ReviewDayOfMonth) + *p = x + return p +} + +func (x ReviewDayOfMonth) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ReviewDayOfMonth) Descriptor() protoreflect.EnumDescriptor { + return file_teleport_accesslist_v1_accesslist_proto_enumTypes[1].Descriptor() +} + +func (ReviewDayOfMonth) Type() protoreflect.EnumType { + return &file_teleport_accesslist_v1_accesslist_proto_enumTypes[1] +} + +func (x ReviewDayOfMonth) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ReviewDayOfMonth.Descriptor instead. +func (ReviewDayOfMonth) EnumDescriptor() ([]byte, []int) { + return file_teleport_accesslist_v1_accesslist_proto_rawDescGZIP(), []int{1} +} + // IneligibleStatus describes how the user is ineligible. type IneligibleStatus int32 @@ -86,11 +194,11 @@ func (x IneligibleStatus) String() string { } func (IneligibleStatus) Descriptor() protoreflect.EnumDescriptor { - return file_teleport_accesslist_v1_accesslist_proto_enumTypes[0].Descriptor() + return file_teleport_accesslist_v1_accesslist_proto_enumTypes[2].Descriptor() } func (IneligibleStatus) Type() protoreflect.EnumType { - return &file_teleport_accesslist_v1_accesslist_proto_enumTypes[0] + return &file_teleport_accesslist_v1_accesslist_proto_enumTypes[2] } func (x IneligibleStatus) Number() protoreflect.EnumNumber { @@ -99,7 +207,7 @@ func (x IneligibleStatus) Number() protoreflect.EnumNumber { // Deprecated: Use IneligibleStatus.Descriptor instead. func (IneligibleStatus) EnumDescriptor() ([]byte, []int) { - return file_teleport_accesslist_v1_accesslist_proto_rawDescGZIP(), []int{0} + return file_teleport_accesslist_v1_accesslist_proto_rawDescGZIP(), []int{2} } // AccessList describes the basic building block of access grants, which are @@ -343,10 +451,10 @@ type AccessListAudit struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - // frequency is a duration that describes how often an access list must be audited. - Frequency *durationpb.Duration `protobuf:"bytes,1,opt,name=frequency,proto3" json:"frequency,omitempty"` // next_audit_date is when the next audit date should be done by. NextAuditDate *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=next_audit_date,json=nextAuditDate,proto3" json:"next_audit_date,omitempty"` + // recurrence is the recurrence definition + Recurrence *Recurrence `protobuf:"bytes,3,opt,name=recurrence,proto3" json:"recurrence,omitempty"` } func (x *AccessListAudit) Reset() { @@ -381,20 +489,78 @@ func (*AccessListAudit) Descriptor() ([]byte, []int) { return file_teleport_accesslist_v1_accesslist_proto_rawDescGZIP(), []int{3} } -func (x *AccessListAudit) GetFrequency() *durationpb.Duration { +func (x *AccessListAudit) GetNextAuditDate() *timestamppb.Timestamp { if x != nil { - return x.Frequency + return x.NextAuditDate } return nil } -func (x *AccessListAudit) GetNextAuditDate() *timestamppb.Timestamp { +func (x *AccessListAudit) GetRecurrence() *Recurrence { if x != nil { - return x.NextAuditDate + return x.Recurrence } return nil } +// Recurrence is the definition for when reviews will be scheduled. +type Recurrence struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // frequency is the frequency of reviews. + Frequency ReviewFrequency `protobuf:"varint,1,opt,name=frequency,proto3,enum=teleport.accesslist.v1.ReviewFrequency" json:"frequency,omitempty"` + // day_of_month is the day of month that reviews will be scheduled on. + DayOfMonth ReviewDayOfMonth `protobuf:"varint,2,opt,name=day_of_month,json=dayOfMonth,proto3,enum=teleport.accesslist.v1.ReviewDayOfMonth" json:"day_of_month,omitempty"` +} + +func (x *Recurrence) Reset() { + *x = Recurrence{} + if protoimpl.UnsafeEnabled { + mi := &file_teleport_accesslist_v1_accesslist_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Recurrence) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Recurrence) ProtoMessage() {} + +func (x *Recurrence) ProtoReflect() protoreflect.Message { + mi := &file_teleport_accesslist_v1_accesslist_proto_msgTypes[4] + 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 Recurrence.ProtoReflect.Descriptor instead. +func (*Recurrence) Descriptor() ([]byte, []int) { + return file_teleport_accesslist_v1_accesslist_proto_rawDescGZIP(), []int{4} +} + +func (x *Recurrence) GetFrequency() ReviewFrequency { + if x != nil { + return x.Frequency + } + return ReviewFrequency_REVIEW_FREQUENCY_UNSPECIFIED +} + +func (x *Recurrence) GetDayOfMonth() ReviewDayOfMonth { + if x != nil { + return x.DayOfMonth + } + return ReviewDayOfMonth_REVIEW_DAY_OF_MONTH_UNSPECIFIED +} + // AccessListRequires describes a requirement section for an access list. A user must // meet the following criteria to obtain the specific access to the list. type AccessListRequires struct { @@ -411,7 +577,7 @@ type AccessListRequires struct { func (x *AccessListRequires) Reset() { *x = AccessListRequires{} if protoimpl.UnsafeEnabled { - mi := &file_teleport_accesslist_v1_accesslist_proto_msgTypes[4] + mi := &file_teleport_accesslist_v1_accesslist_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -424,7 +590,7 @@ func (x *AccessListRequires) String() string { func (*AccessListRequires) ProtoMessage() {} func (x *AccessListRequires) ProtoReflect() protoreflect.Message { - mi := &file_teleport_accesslist_v1_accesslist_proto_msgTypes[4] + mi := &file_teleport_accesslist_v1_accesslist_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -437,7 +603,7 @@ func (x *AccessListRequires) ProtoReflect() protoreflect.Message { // Deprecated: Use AccessListRequires.ProtoReflect.Descriptor instead. func (*AccessListRequires) Descriptor() ([]byte, []int) { - return file_teleport_accesslist_v1_accesslist_proto_rawDescGZIP(), []int{4} + return file_teleport_accesslist_v1_accesslist_proto_rawDescGZIP(), []int{5} } func (x *AccessListRequires) GetRoles() []string { @@ -469,7 +635,7 @@ type AccessListGrants struct { func (x *AccessListGrants) Reset() { *x = AccessListGrants{} if protoimpl.UnsafeEnabled { - mi := &file_teleport_accesslist_v1_accesslist_proto_msgTypes[5] + mi := &file_teleport_accesslist_v1_accesslist_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -482,7 +648,7 @@ func (x *AccessListGrants) String() string { func (*AccessListGrants) ProtoMessage() {} func (x *AccessListGrants) ProtoReflect() protoreflect.Message { - mi := &file_teleport_accesslist_v1_accesslist_proto_msgTypes[5] + mi := &file_teleport_accesslist_v1_accesslist_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -495,7 +661,7 @@ func (x *AccessListGrants) ProtoReflect() protoreflect.Message { // Deprecated: Use AccessListGrants.ProtoReflect.Descriptor instead. func (*AccessListGrants) Descriptor() ([]byte, []int) { - return file_teleport_accesslist_v1_accesslist_proto_rawDescGZIP(), []int{5} + return file_teleport_accesslist_v1_accesslist_proto_rawDescGZIP(), []int{6} } func (x *AccessListGrants) GetRoles() []string { @@ -527,7 +693,7 @@ type Member struct { func (x *Member) Reset() { *x = Member{} if protoimpl.UnsafeEnabled { - mi := &file_teleport_accesslist_v1_accesslist_proto_msgTypes[6] + mi := &file_teleport_accesslist_v1_accesslist_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -540,7 +706,7 @@ func (x *Member) String() string { func (*Member) ProtoMessage() {} func (x *Member) ProtoReflect() protoreflect.Message { - mi := &file_teleport_accesslist_v1_accesslist_proto_msgTypes[6] + mi := &file_teleport_accesslist_v1_accesslist_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -553,7 +719,7 @@ func (x *Member) ProtoReflect() protoreflect.Message { // Deprecated: Use Member.ProtoReflect.Descriptor instead. func (*Member) Descriptor() ([]byte, []int) { - return file_teleport_accesslist_v1_accesslist_proto_rawDescGZIP(), []int{6} + return file_teleport_accesslist_v1_accesslist_proto_rawDescGZIP(), []int{7} } func (x *Member) GetHeader() *v1.ResourceHeader { @@ -596,7 +762,7 @@ type MemberSpec struct { func (x *MemberSpec) Reset() { *x = MemberSpec{} if protoimpl.UnsafeEnabled { - mi := &file_teleport_accesslist_v1_accesslist_proto_msgTypes[7] + mi := &file_teleport_accesslist_v1_accesslist_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -609,7 +775,7 @@ func (x *MemberSpec) String() string { func (*MemberSpec) ProtoMessage() {} func (x *MemberSpec) ProtoReflect() protoreflect.Message { - mi := &file_teleport_accesslist_v1_accesslist_proto_msgTypes[7] + mi := &file_teleport_accesslist_v1_accesslist_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -622,7 +788,7 @@ func (x *MemberSpec) ProtoReflect() protoreflect.Message { // Deprecated: Use MemberSpec.ProtoReflect.Descriptor instead. func (*MemberSpec) Descriptor() ([]byte, []int) { - return file_teleport_accesslist_v1_accesslist_proto_rawDescGZIP(), []int{7} + return file_teleport_accesslist_v1_accesslist_proto_rawDescGZIP(), []int{8} } func (x *MemberSpec) GetAccessList() string { @@ -689,7 +855,7 @@ type Review struct { func (x *Review) Reset() { *x = Review{} if protoimpl.UnsafeEnabled { - mi := &file_teleport_accesslist_v1_accesslist_proto_msgTypes[8] + mi := &file_teleport_accesslist_v1_accesslist_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -702,7 +868,7 @@ func (x *Review) String() string { func (*Review) ProtoMessage() {} func (x *Review) ProtoReflect() protoreflect.Message { - mi := &file_teleport_accesslist_v1_accesslist_proto_msgTypes[8] + mi := &file_teleport_accesslist_v1_accesslist_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -715,7 +881,7 @@ func (x *Review) ProtoReflect() protoreflect.Message { // Deprecated: Use Review.ProtoReflect.Descriptor instead. func (*Review) Descriptor() ([]byte, []int) { - return file_teleport_accesslist_v1_accesslist_proto_rawDescGZIP(), []int{8} + return file_teleport_accesslist_v1_accesslist_proto_rawDescGZIP(), []int{9} } func (x *Review) GetHeader() *v1.ResourceHeader { @@ -754,7 +920,7 @@ type ReviewSpec struct { func (x *ReviewSpec) Reset() { *x = ReviewSpec{} if protoimpl.UnsafeEnabled { - mi := &file_teleport_accesslist_v1_accesslist_proto_msgTypes[9] + mi := &file_teleport_accesslist_v1_accesslist_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -767,7 +933,7 @@ func (x *ReviewSpec) String() string { func (*ReviewSpec) ProtoMessage() {} func (x *ReviewSpec) ProtoReflect() protoreflect.Message { - mi := &file_teleport_accesslist_v1_accesslist_proto_msgTypes[9] + mi := &file_teleport_accesslist_v1_accesslist_proto_msgTypes[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -780,7 +946,7 @@ func (x *ReviewSpec) ProtoReflect() protoreflect.Message { // Deprecated: Use ReviewSpec.ProtoReflect.Descriptor instead. func (*ReviewSpec) Descriptor() ([]byte, []int) { - return file_teleport_accesslist_v1_accesslist_proto_rawDescGZIP(), []int{9} + return file_teleport_accesslist_v1_accesslist_proto_rawDescGZIP(), []int{10} } func (x *ReviewSpec) GetAccessList() string { @@ -824,18 +990,20 @@ type ReviewChanges struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - // audit_frequency_changed is populated if the audit frequency was changed. - FrequencyChanged *durationpb.Duration `protobuf:"bytes,1,opt,name=frequency_changed,json=frequencyChanged,proto3" json:"frequency_changed,omitempty"` // membership_requirements_changed is populated if the requirements were changed as part of this review. MembershipRequirementsChanged *AccessListRequires `protobuf:"bytes,2,opt,name=membership_requirements_changed,json=membershipRequirementsChanged,proto3" json:"membership_requirements_changed,omitempty"` // removed_members contains the members that were removed as part of this review. RemovedMembers []string `protobuf:"bytes,3,rep,name=removed_members,json=removedMembers,proto3" json:"removed_members,omitempty"` + // review_frequency_changed is populated if the review frequency has changed. + ReviewFrequencyChanged ReviewFrequency `protobuf:"varint,4,opt,name=review_frequency_changed,json=reviewFrequencyChanged,proto3,enum=teleport.accesslist.v1.ReviewFrequency" json:"review_frequency_changed,omitempty"` + // review_day_of_month changed is populated if the review day of month has changed. + ReviewDayOfMonthChanged ReviewDayOfMonth `protobuf:"varint,5,opt,name=review_day_of_month_changed,json=reviewDayOfMonthChanged,proto3,enum=teleport.accesslist.v1.ReviewDayOfMonth" json:"review_day_of_month_changed,omitempty"` } func (x *ReviewChanges) Reset() { *x = ReviewChanges{} if protoimpl.UnsafeEnabled { - mi := &file_teleport_accesslist_v1_accesslist_proto_msgTypes[10] + mi := &file_teleport_accesslist_v1_accesslist_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -848,7 +1016,7 @@ func (x *ReviewChanges) String() string { func (*ReviewChanges) ProtoMessage() {} func (x *ReviewChanges) ProtoReflect() protoreflect.Message { - mi := &file_teleport_accesslist_v1_accesslist_proto_msgTypes[10] + mi := &file_teleport_accesslist_v1_accesslist_proto_msgTypes[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -861,14 +1029,7 @@ func (x *ReviewChanges) ProtoReflect() protoreflect.Message { // Deprecated: Use ReviewChanges.ProtoReflect.Descriptor instead. func (*ReviewChanges) Descriptor() ([]byte, []int) { - return file_teleport_accesslist_v1_accesslist_proto_rawDescGZIP(), []int{10} -} - -func (x *ReviewChanges) GetFrequencyChanged() *durationpb.Duration { - if x != nil { - return x.FrequencyChanged - } - return nil + return file_teleport_accesslist_v1_accesslist_proto_rawDescGZIP(), []int{11} } func (x *ReviewChanges) GetMembershipRequirementsChanged() *AccessListRequires { @@ -885,6 +1046,20 @@ func (x *ReviewChanges) GetRemovedMembers() []string { return nil } +func (x *ReviewChanges) GetReviewFrequencyChanged() ReviewFrequency { + if x != nil { + return x.ReviewFrequencyChanged + } + return ReviewFrequency_REVIEW_FREQUENCY_UNSPECIFIED +} + +func (x *ReviewChanges) GetReviewDayOfMonthChanged() ReviewDayOfMonth { + if x != nil { + return x.ReviewDayOfMonthChanged + } + return ReviewDayOfMonth_REVIEW_DAY_OF_MONTH_UNSPECIFIED +} + var File_teleport_accesslist_v1_accesslist_proto protoreflect.FileDescriptor var file_teleport_accesslist_v1_accesslist_proto_rawDesc = []byte{ @@ -892,9 +1067,7 @@ var file_teleport_accesslist_v1_accesslist_proto_rawDesc = []byte{ 0x73, 0x6c, 0x69, 0x73, 0x74, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x6c, 0x69, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x16, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x6c, 0x69, 0x73, 0x74, 0x2e, 0x76, - 0x31, 0x1a, 0x1e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x31, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x27, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x68, @@ -948,111 +1121,154 @@ var file_teleport_accesslist_v1_accesslist_proto_rawDesc = []byte{ 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x6c, 0x69, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x65, 0x6c, 0x69, 0x67, 0x69, 0x62, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x10, 0x69, 0x6e, 0x65, 0x6c, 0x69, 0x67, 0x69, 0x62, 0x6c, 0x65, - 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x8e, 0x01, 0x0a, 0x0f, 0x41, 0x63, 0x63, 0x65, 0x73, - 0x73, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x75, 0x64, 0x69, 0x74, 0x12, 0x37, 0x0a, 0x09, 0x66, 0x72, - 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, - 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x09, 0x66, 0x72, 0x65, 0x71, 0x75, 0x65, - 0x6e, 0x63, 0x79, 0x12, 0x42, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x61, 0x75, 0x64, 0x69, - 0x74, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x02, 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, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x41, 0x75, - 0x64, 0x69, 0x74, 0x44, 0x61, 0x74, 0x65, 0x22, 0x5c, 0x0a, 0x12, 0x41, 0x63, 0x63, 0x65, 0x73, - 0x73, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x73, 0x12, 0x14, 0x0a, - 0x05, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x72, 0x6f, - 0x6c, 0x65, 0x73, 0x12, 0x30, 0x0a, 0x06, 0x74, 0x72, 0x61, 0x69, 0x74, 0x73, 0x18, 0x02, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x74, - 0x72, 0x61, 0x69, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x72, 0x61, 0x69, 0x74, 0x52, 0x06, 0x74, - 0x72, 0x61, 0x69, 0x74, 0x73, 0x22, 0x5a, 0x0a, 0x10, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x4c, - 0x69, 0x73, 0x74, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x6f, 0x6c, - 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x12, - 0x30, 0x0a, 0x06, 0x74, 0x72, 0x61, 0x69, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x18, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x74, 0x72, 0x61, 0x69, 0x74, - 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x72, 0x61, 0x69, 0x74, 0x52, 0x06, 0x74, 0x72, 0x61, 0x69, 0x74, - 0x73, 0x22, 0x7c, 0x0a, 0x06, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x3a, 0x0a, 0x06, 0x68, - 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x74, 0x65, - 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x2e, 0x76, 0x31, - 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, - 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x36, 0x0a, 0x04, 0x73, 0x70, 0x65, 0x63, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, - 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x6c, 0x69, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4d, - 0x65, 0x6d, 0x62, 0x65, 0x72, 0x53, 0x70, 0x65, 0x63, 0x52, 0x04, 0x73, 0x70, 0x65, 0x63, 0x22, - 0xb5, 0x02, 0x0a, 0x0a, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x53, 0x70, 0x65, 0x63, 0x12, 0x1f, - 0x0a, 0x0b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x4c, 0x69, 0x73, 0x74, 0x12, - 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, - 0x61, 0x6d, 0x65, 0x12, 0x32, 0x0a, 0x06, 0x6a, 0x6f, 0x69, 0x6e, 0x65, 0x64, 0x18, 0x03, 0x20, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0xaa, 0x01, 0x0a, 0x0f, 0x41, 0x63, 0x63, 0x65, 0x73, + 0x73, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x75, 0x64, 0x69, 0x74, 0x12, 0x42, 0x0a, 0x0f, 0x6e, 0x65, + 0x78, 0x74, 0x5f, 0x61, 0x75, 0x64, 0x69, 0x74, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x02, 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, - 0x06, 0x6a, 0x6f, 0x69, 0x6e, 0x65, 0x64, 0x12, 0x34, 0x0a, 0x07, 0x65, 0x78, 0x70, 0x69, 0x72, - 0x65, 0x73, 0x18, 0x04, 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, 0x07, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x12, 0x16, 0x0a, - 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, - 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x64, 0x64, 0x65, 0x64, 0x5f, 0x62, - 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x65, 0x64, 0x42, 0x79, - 0x12, 0x55, 0x0a, 0x11, 0x69, 0x6e, 0x65, 0x6c, 0x69, 0x67, 0x69, 0x62, 0x6c, 0x65, 0x5f, 0x73, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x28, 0x2e, 0x74, 0x65, - 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x6c, 0x69, 0x73, - 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x65, 0x6c, 0x69, 0x67, 0x69, 0x62, 0x6c, 0x65, 0x53, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x10, 0x69, 0x6e, 0x65, 0x6c, 0x69, 0x67, 0x69, 0x62, 0x6c, - 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x7c, 0x0a, 0x06, 0x52, 0x65, 0x76, 0x69, 0x65, - 0x77, 0x12, 0x3a, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x22, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x68, 0x65, 0x61, - 0x64, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x48, - 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x36, 0x0a, - 0x04, 0x73, 0x70, 0x65, 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x74, 0x65, - 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x6c, 0x69, 0x73, - 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x76, 0x69, 0x65, 0x77, 0x53, 0x70, 0x65, 0x63, 0x52, - 0x04, 0x73, 0x70, 0x65, 0x63, 0x22, 0xdf, 0x01, 0x0a, 0x0a, 0x52, 0x65, 0x76, 0x69, 0x65, 0x77, - 0x53, 0x70, 0x65, 0x63, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x6c, - 0x69, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x63, 0x63, 0x65, 0x73, - 0x73, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x72, 0x65, 0x76, 0x69, 0x65, 0x77, 0x65, - 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x76, 0x69, 0x65, 0x77, - 0x65, 0x72, 0x73, 0x12, 0x3b, 0x0a, 0x0b, 0x72, 0x65, 0x76, 0x69, 0x65, 0x77, 0x5f, 0x64, 0x61, - 0x74, 0x65, 0x18, 0x03, 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, 0x0a, 0x72, 0x65, 0x76, 0x69, 0x65, 0x77, 0x44, 0x61, 0x74, 0x65, - 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x6f, 0x74, 0x65, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x05, 0x6e, 0x6f, 0x74, 0x65, 0x73, 0x12, 0x3f, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, - 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, - 0x72, 0x74, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x6c, 0x69, 0x73, 0x74, 0x2e, 0x76, 0x31, - 0x2e, 0x52, 0x65, 0x76, 0x69, 0x65, 0x77, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x52, 0x07, - 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x22, 0xf4, 0x01, 0x0a, 0x0d, 0x52, 0x65, 0x76, 0x69, - 0x65, 0x77, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x12, 0x46, 0x0a, 0x11, 0x66, 0x72, 0x65, - 0x71, 0x75, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, - 0x10, 0x66, 0x72, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x79, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, - 0x64, 0x12, 0x72, 0x0a, 0x1f, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x5f, - 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x5f, 0x63, 0x68, 0x61, - 0x6e, 0x67, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x74, 0x65, 0x6c, - 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x6c, 0x69, 0x73, 0x74, - 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, - 0x71, 0x75, 0x69, 0x72, 0x65, 0x73, 0x52, 0x1d, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, - 0x69, 0x70, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x43, 0x68, - 0x61, 0x6e, 0x67, 0x65, 0x64, 0x12, 0x27, 0x0a, 0x0f, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, - 0x5f, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, - 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x2a, 0xc6, - 0x01, 0x0a, 0x10, 0x49, 0x6e, 0x65, 0x6c, 0x69, 0x67, 0x69, 0x62, 0x6c, 0x65, 0x53, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x12, 0x21, 0x0a, 0x1d, 0x49, 0x4e, 0x45, 0x4c, 0x49, 0x47, 0x49, 0x42, 0x4c, - 0x45, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, - 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1e, 0x0a, 0x1a, 0x49, 0x4e, 0x45, 0x4c, 0x49, 0x47, - 0x49, 0x42, 0x4c, 0x45, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x45, 0x4c, 0x49, 0x47, - 0x49, 0x42, 0x4c, 0x45, 0x10, 0x01, 0x12, 0x24, 0x0a, 0x20, 0x49, 0x4e, 0x45, 0x4c, 0x49, 0x47, - 0x49, 0x42, 0x4c, 0x45, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x55, 0x53, 0x45, 0x52, - 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x45, 0x58, 0x49, 0x53, 0x54, 0x10, 0x02, 0x12, 0x2a, 0x0a, 0x26, - 0x49, 0x4e, 0x45, 0x4c, 0x49, 0x47, 0x49, 0x42, 0x4c, 0x45, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, - 0x53, 0x5f, 0x4d, 0x49, 0x53, 0x53, 0x49, 0x4e, 0x47, 0x5f, 0x52, 0x45, 0x51, 0x55, 0x49, 0x52, - 0x45, 0x4d, 0x45, 0x4e, 0x54, 0x53, 0x10, 0x03, 0x12, 0x1d, 0x0a, 0x19, 0x49, 0x4e, 0x45, 0x4c, - 0x49, 0x47, 0x49, 0x42, 0x4c, 0x45, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x45, 0x58, - 0x50, 0x49, 0x52, 0x45, 0x44, 0x10, 0x04, 0x42, 0x58, 0x5a, 0x56, 0x67, 0x69, 0x74, 0x68, 0x75, - 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x72, 0x61, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x61, 0x6c, 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x61, 0x70, 0x69, - 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x6f, 0x2f, 0x74, 0x65, - 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x6c, 0x69, 0x73, - 0x74, 0x2f, 0x76, 0x31, 0x3b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x6c, 0x69, 0x73, 0x74, 0x76, - 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x41, 0x75, 0x64, 0x69, 0x74, 0x44, 0x61, 0x74, 0x65, 0x12, 0x42, + 0x0a, 0x0a, 0x72, 0x65, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x61, 0x63, + 0x63, 0x65, 0x73, 0x73, 0x6c, 0x69, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x63, 0x75, + 0x72, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x0a, 0x72, 0x65, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, + 0x63, 0x65, 0x4a, 0x04, 0x08, 0x01, 0x10, 0x02, 0x52, 0x09, 0x66, 0x72, 0x65, 0x71, 0x75, 0x65, + 0x6e, 0x63, 0x79, 0x22, 0x9f, 0x01, 0x0a, 0x0a, 0x52, 0x65, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, + 0x63, 0x65, 0x12, 0x45, 0x0a, 0x09, 0x66, 0x72, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x79, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x27, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, + 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x6c, 0x69, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, + 0x65, 0x76, 0x69, 0x65, 0x77, 0x46, 0x72, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x09, + 0x66, 0x72, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x4a, 0x0a, 0x0c, 0x64, 0x61, 0x79, + 0x5f, 0x6f, 0x66, 0x5f, 0x6d, 0x6f, 0x6e, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x28, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, + 0x73, 0x6c, 0x69, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x76, 0x69, 0x65, 0x77, 0x44, + 0x61, 0x79, 0x4f, 0x66, 0x4d, 0x6f, 0x6e, 0x74, 0x68, 0x52, 0x0a, 0x64, 0x61, 0x79, 0x4f, 0x66, + 0x4d, 0x6f, 0x6e, 0x74, 0x68, 0x22, 0x5c, 0x0a, 0x12, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x4c, + 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x72, + 0x6f, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x72, 0x6f, 0x6c, 0x65, + 0x73, 0x12, 0x30, 0x0a, 0x06, 0x74, 0x72, 0x61, 0x69, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x18, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x74, 0x72, 0x61, + 0x69, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x72, 0x61, 0x69, 0x74, 0x52, 0x06, 0x74, 0x72, 0x61, + 0x69, 0x74, 0x73, 0x22, 0x5a, 0x0a, 0x10, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x4c, 0x69, 0x73, + 0x74, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x6f, 0x6c, 0x65, 0x73, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x12, 0x30, 0x0a, + 0x06, 0x74, 0x72, 0x61, 0x69, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, + 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x74, 0x72, 0x61, 0x69, 0x74, 0x2e, 0x76, + 0x31, 0x2e, 0x54, 0x72, 0x61, 0x69, 0x74, 0x52, 0x06, 0x74, 0x72, 0x61, 0x69, 0x74, 0x73, 0x22, + 0x7c, 0x0a, 0x06, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x3a, 0x0a, 0x06, 0x68, 0x65, 0x61, + 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x74, 0x65, 0x6c, 0x65, + 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x06, 0x68, + 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x36, 0x0a, 0x04, 0x73, 0x70, 0x65, 0x63, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x61, + 0x63, 0x63, 0x65, 0x73, 0x73, 0x6c, 0x69, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x6d, + 0x62, 0x65, 0x72, 0x53, 0x70, 0x65, 0x63, 0x52, 0x04, 0x73, 0x70, 0x65, 0x63, 0x22, 0xb5, 0x02, + 0x0a, 0x0a, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x53, 0x70, 0x65, 0x63, 0x12, 0x1f, 0x0a, 0x0b, + 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0a, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x12, 0x0a, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x12, 0x32, 0x0a, 0x06, 0x6a, 0x6f, 0x69, 0x6e, 0x65, 0x64, 0x18, 0x03, 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, 0x06, 0x6a, + 0x6f, 0x69, 0x6e, 0x65, 0x64, 0x12, 0x34, 0x0a, 0x07, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, + 0x18, 0x04, 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, 0x07, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x72, + 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, + 0x73, 0x6f, 0x6e, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x64, 0x64, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x65, 0x64, 0x42, 0x79, 0x12, 0x55, + 0x0a, 0x11, 0x69, 0x6e, 0x65, 0x6c, 0x69, 0x67, 0x69, 0x62, 0x6c, 0x65, 0x5f, 0x73, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x28, 0x2e, 0x74, 0x65, 0x6c, 0x65, + 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x6c, 0x69, 0x73, 0x74, 0x2e, + 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x65, 0x6c, 0x69, 0x67, 0x69, 0x62, 0x6c, 0x65, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x52, 0x10, 0x69, 0x6e, 0x65, 0x6c, 0x69, 0x67, 0x69, 0x62, 0x6c, 0x65, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x7c, 0x0a, 0x06, 0x52, 0x65, 0x76, 0x69, 0x65, 0x77, 0x12, + 0x3a, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x22, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x48, 0x65, 0x61, + 0x64, 0x65, 0x72, 0x52, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x36, 0x0a, 0x04, 0x73, + 0x70, 0x65, 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x74, 0x65, 0x6c, 0x65, + 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x6c, 0x69, 0x73, 0x74, 0x2e, + 0x76, 0x31, 0x2e, 0x52, 0x65, 0x76, 0x69, 0x65, 0x77, 0x53, 0x70, 0x65, 0x63, 0x52, 0x04, 0x73, + 0x70, 0x65, 0x63, 0x22, 0xdf, 0x01, 0x0a, 0x0a, 0x52, 0x65, 0x76, 0x69, 0x65, 0x77, 0x53, 0x70, + 0x65, 0x63, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x6c, 0x69, 0x73, + 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x4c, + 0x69, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x72, 0x65, 0x76, 0x69, 0x65, 0x77, 0x65, 0x72, 0x73, + 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x76, 0x69, 0x65, 0x77, 0x65, 0x72, + 0x73, 0x12, 0x3b, 0x0a, 0x0b, 0x72, 0x65, 0x76, 0x69, 0x65, 0x77, 0x5f, 0x64, 0x61, 0x74, 0x65, + 0x18, 0x03, 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, 0x0a, 0x72, 0x65, 0x76, 0x69, 0x65, 0x77, 0x44, 0x61, 0x74, 0x65, 0x12, 0x14, + 0x0a, 0x05, 0x6e, 0x6f, 0x74, 0x65, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6e, + 0x6f, 0x74, 0x65, 0x73, 0x12, 0x3f, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, + 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x6c, 0x69, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, + 0x65, 0x76, 0x69, 0x65, 0x77, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x52, 0x07, 0x63, 0x68, + 0x61, 0x6e, 0x67, 0x65, 0x73, 0x22, 0x90, 0x03, 0x0a, 0x0d, 0x52, 0x65, 0x76, 0x69, 0x65, 0x77, + 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x12, 0x72, 0x0a, 0x1f, 0x6d, 0x65, 0x6d, 0x62, 0x65, + 0x72, 0x73, 0x68, 0x69, 0x70, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x6d, 0x65, 0x6e, + 0x74, 0x73, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x2a, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x61, 0x63, 0x63, 0x65, + 0x73, 0x73, 0x6c, 0x69, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, + 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x73, 0x52, 0x1d, 0x6d, 0x65, + 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x6d, + 0x65, 0x6e, 0x74, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x12, 0x27, 0x0a, 0x0f, 0x72, + 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x5f, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x18, 0x03, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x4d, 0x65, 0x6d, + 0x62, 0x65, 0x72, 0x73, 0x12, 0x61, 0x0a, 0x18, 0x72, 0x65, 0x76, 0x69, 0x65, 0x77, 0x5f, 0x66, + 0x72, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x27, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, + 0x74, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x6c, 0x69, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, + 0x52, 0x65, 0x76, 0x69, 0x65, 0x77, 0x46, 0x72, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x79, 0x52, + 0x16, 0x72, 0x65, 0x76, 0x69, 0x65, 0x77, 0x46, 0x72, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x79, + 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x12, 0x66, 0x0a, 0x1b, 0x72, 0x65, 0x76, 0x69, 0x65, + 0x77, 0x5f, 0x64, 0x61, 0x79, 0x5f, 0x6f, 0x66, 0x5f, 0x6d, 0x6f, 0x6e, 0x74, 0x68, 0x5f, 0x63, + 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x28, 0x2e, 0x74, + 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x6c, 0x69, + 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x76, 0x69, 0x65, 0x77, 0x44, 0x61, 0x79, 0x4f, + 0x66, 0x4d, 0x6f, 0x6e, 0x74, 0x68, 0x52, 0x17, 0x72, 0x65, 0x76, 0x69, 0x65, 0x77, 0x44, 0x61, + 0x79, 0x4f, 0x66, 0x4d, 0x6f, 0x6e, 0x74, 0x68, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x4a, + 0x04, 0x08, 0x01, 0x10, 0x02, 0x52, 0x11, 0x66, 0x72, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x79, + 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x2a, 0xb6, 0x01, 0x0a, 0x0f, 0x52, 0x65, 0x76, + 0x69, 0x65, 0x77, 0x46, 0x72, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x20, 0x0a, 0x1c, + 0x52, 0x45, 0x56, 0x49, 0x45, 0x57, 0x5f, 0x46, 0x52, 0x45, 0x51, 0x55, 0x45, 0x4e, 0x43, 0x59, + 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1e, + 0x0a, 0x1a, 0x52, 0x45, 0x56, 0x49, 0x45, 0x57, 0x5f, 0x46, 0x52, 0x45, 0x51, 0x55, 0x45, 0x4e, + 0x43, 0x59, 0x5f, 0x4f, 0x4e, 0x45, 0x5f, 0x4d, 0x4f, 0x4e, 0x54, 0x48, 0x10, 0x01, 0x12, 0x21, + 0x0a, 0x1d, 0x52, 0x45, 0x56, 0x49, 0x45, 0x57, 0x5f, 0x46, 0x52, 0x45, 0x51, 0x55, 0x45, 0x4e, + 0x43, 0x59, 0x5f, 0x54, 0x48, 0x52, 0x45, 0x45, 0x5f, 0x4d, 0x4f, 0x4e, 0x54, 0x48, 0x53, 0x10, + 0x03, 0x12, 0x1f, 0x0a, 0x1b, 0x52, 0x45, 0x56, 0x49, 0x45, 0x57, 0x5f, 0x46, 0x52, 0x45, 0x51, + 0x55, 0x45, 0x4e, 0x43, 0x59, 0x5f, 0x53, 0x49, 0x58, 0x5f, 0x4d, 0x4f, 0x4e, 0x54, 0x48, 0x53, + 0x10, 0x06, 0x12, 0x1d, 0x0a, 0x19, 0x52, 0x45, 0x56, 0x49, 0x45, 0x57, 0x5f, 0x46, 0x52, 0x45, + 0x51, 0x55, 0x45, 0x4e, 0x43, 0x59, 0x5f, 0x4f, 0x4e, 0x45, 0x5f, 0x59, 0x45, 0x41, 0x52, 0x10, + 0x0c, 0x2a, 0x97, 0x01, 0x0a, 0x10, 0x52, 0x65, 0x76, 0x69, 0x65, 0x77, 0x44, 0x61, 0x79, 0x4f, + 0x66, 0x4d, 0x6f, 0x6e, 0x74, 0x68, 0x12, 0x23, 0x0a, 0x1f, 0x52, 0x45, 0x56, 0x49, 0x45, 0x57, + 0x5f, 0x44, 0x41, 0x59, 0x5f, 0x4f, 0x46, 0x5f, 0x4d, 0x4f, 0x4e, 0x54, 0x48, 0x5f, 0x55, 0x4e, + 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1d, 0x0a, 0x19, 0x52, + 0x45, 0x56, 0x49, 0x45, 0x57, 0x5f, 0x44, 0x41, 0x59, 0x5f, 0x4f, 0x46, 0x5f, 0x4d, 0x4f, 0x4e, + 0x54, 0x48, 0x5f, 0x46, 0x49, 0x52, 0x53, 0x54, 0x10, 0x01, 0x12, 0x21, 0x0a, 0x1d, 0x52, 0x45, + 0x56, 0x49, 0x45, 0x57, 0x5f, 0x44, 0x41, 0x59, 0x5f, 0x4f, 0x46, 0x5f, 0x4d, 0x4f, 0x4e, 0x54, + 0x48, 0x5f, 0x46, 0x49, 0x46, 0x54, 0x45, 0x45, 0x4e, 0x54, 0x48, 0x10, 0x0f, 0x12, 0x1c, 0x0a, + 0x18, 0x52, 0x45, 0x56, 0x49, 0x45, 0x57, 0x5f, 0x44, 0x41, 0x59, 0x5f, 0x4f, 0x46, 0x5f, 0x4d, + 0x4f, 0x4e, 0x54, 0x48, 0x5f, 0x4c, 0x41, 0x53, 0x54, 0x10, 0x1f, 0x2a, 0xc6, 0x01, 0x0a, 0x10, + 0x49, 0x6e, 0x65, 0x6c, 0x69, 0x67, 0x69, 0x62, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x12, 0x21, 0x0a, 0x1d, 0x49, 0x4e, 0x45, 0x4c, 0x49, 0x47, 0x49, 0x42, 0x4c, 0x45, 0x5f, 0x53, + 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, + 0x44, 0x10, 0x00, 0x12, 0x1e, 0x0a, 0x1a, 0x49, 0x4e, 0x45, 0x4c, 0x49, 0x47, 0x49, 0x42, 0x4c, + 0x45, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x45, 0x4c, 0x49, 0x47, 0x49, 0x42, 0x4c, + 0x45, 0x10, 0x01, 0x12, 0x24, 0x0a, 0x20, 0x49, 0x4e, 0x45, 0x4c, 0x49, 0x47, 0x49, 0x42, 0x4c, + 0x45, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x55, 0x53, 0x45, 0x52, 0x5f, 0x4e, 0x4f, + 0x54, 0x5f, 0x45, 0x58, 0x49, 0x53, 0x54, 0x10, 0x02, 0x12, 0x2a, 0x0a, 0x26, 0x49, 0x4e, 0x45, + 0x4c, 0x49, 0x47, 0x49, 0x42, 0x4c, 0x45, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x4d, + 0x49, 0x53, 0x53, 0x49, 0x4e, 0x47, 0x5f, 0x52, 0x45, 0x51, 0x55, 0x49, 0x52, 0x45, 0x4d, 0x45, + 0x4e, 0x54, 0x53, 0x10, 0x03, 0x12, 0x1d, 0x0a, 0x19, 0x49, 0x4e, 0x45, 0x4c, 0x49, 0x47, 0x49, + 0x42, 0x4c, 0x45, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x45, 0x58, 0x50, 0x49, 0x52, + 0x45, 0x44, 0x10, 0x04, 0x42, 0x58, 0x5a, 0x56, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, + 0x6f, 0x6d, 0x2f, 0x67, 0x72, 0x61, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, + 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x65, + 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x6f, 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x70, + 0x6f, 0x72, 0x74, 0x2f, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x6c, 0x69, 0x73, 0x74, 0x2f, 0x76, + 0x31, 0x3b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x6c, 0x69, 0x73, 0x74, 0x76, 0x31, 0x62, 0x06, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1067,55 +1283,60 @@ func file_teleport_accesslist_v1_accesslist_proto_rawDescGZIP() []byte { return file_teleport_accesslist_v1_accesslist_proto_rawDescData } -var file_teleport_accesslist_v1_accesslist_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_teleport_accesslist_v1_accesslist_proto_msgTypes = make([]protoimpl.MessageInfo, 11) +var file_teleport_accesslist_v1_accesslist_proto_enumTypes = make([]protoimpl.EnumInfo, 3) +var file_teleport_accesslist_v1_accesslist_proto_msgTypes = make([]protoimpl.MessageInfo, 12) var file_teleport_accesslist_v1_accesslist_proto_goTypes = []interface{}{ - (IneligibleStatus)(0), // 0: teleport.accesslist.v1.IneligibleStatus - (*AccessList)(nil), // 1: teleport.accesslist.v1.AccessList - (*AccessListSpec)(nil), // 2: teleport.accesslist.v1.AccessListSpec - (*AccessListOwner)(nil), // 3: teleport.accesslist.v1.AccessListOwner - (*AccessListAudit)(nil), // 4: teleport.accesslist.v1.AccessListAudit - (*AccessListRequires)(nil), // 5: teleport.accesslist.v1.AccessListRequires - (*AccessListGrants)(nil), // 6: teleport.accesslist.v1.AccessListGrants - (*Member)(nil), // 7: teleport.accesslist.v1.Member - (*MemberSpec)(nil), // 8: teleport.accesslist.v1.MemberSpec - (*Review)(nil), // 9: teleport.accesslist.v1.Review - (*ReviewSpec)(nil), // 10: teleport.accesslist.v1.ReviewSpec - (*ReviewChanges)(nil), // 11: teleport.accesslist.v1.ReviewChanges - (*v1.ResourceHeader)(nil), // 12: teleport.header.v1.ResourceHeader - (*durationpb.Duration)(nil), // 13: google.protobuf.Duration - (*timestamppb.Timestamp)(nil), // 14: google.protobuf.Timestamp - (*v11.Trait)(nil), // 15: teleport.trait.v1.Trait + (ReviewFrequency)(0), // 0: teleport.accesslist.v1.ReviewFrequency + (ReviewDayOfMonth)(0), // 1: teleport.accesslist.v1.ReviewDayOfMonth + (IneligibleStatus)(0), // 2: teleport.accesslist.v1.IneligibleStatus + (*AccessList)(nil), // 3: teleport.accesslist.v1.AccessList + (*AccessListSpec)(nil), // 4: teleport.accesslist.v1.AccessListSpec + (*AccessListOwner)(nil), // 5: teleport.accesslist.v1.AccessListOwner + (*AccessListAudit)(nil), // 6: teleport.accesslist.v1.AccessListAudit + (*Recurrence)(nil), // 7: teleport.accesslist.v1.Recurrence + (*AccessListRequires)(nil), // 8: teleport.accesslist.v1.AccessListRequires + (*AccessListGrants)(nil), // 9: teleport.accesslist.v1.AccessListGrants + (*Member)(nil), // 10: teleport.accesslist.v1.Member + (*MemberSpec)(nil), // 11: teleport.accesslist.v1.MemberSpec + (*Review)(nil), // 12: teleport.accesslist.v1.Review + (*ReviewSpec)(nil), // 13: teleport.accesslist.v1.ReviewSpec + (*ReviewChanges)(nil), // 14: teleport.accesslist.v1.ReviewChanges + (*v1.ResourceHeader)(nil), // 15: teleport.header.v1.ResourceHeader + (*timestamppb.Timestamp)(nil), // 16: google.protobuf.Timestamp + (*v11.Trait)(nil), // 17: teleport.trait.v1.Trait } var file_teleport_accesslist_v1_accesslist_proto_depIdxs = []int32{ - 12, // 0: teleport.accesslist.v1.AccessList.header:type_name -> teleport.header.v1.ResourceHeader - 2, // 1: teleport.accesslist.v1.AccessList.spec:type_name -> teleport.accesslist.v1.AccessListSpec - 3, // 2: teleport.accesslist.v1.AccessListSpec.owners:type_name -> teleport.accesslist.v1.AccessListOwner - 4, // 3: teleport.accesslist.v1.AccessListSpec.audit:type_name -> teleport.accesslist.v1.AccessListAudit - 5, // 4: teleport.accesslist.v1.AccessListSpec.membership_requires:type_name -> teleport.accesslist.v1.AccessListRequires - 5, // 5: teleport.accesslist.v1.AccessListSpec.ownership_requires:type_name -> teleport.accesslist.v1.AccessListRequires - 6, // 6: teleport.accesslist.v1.AccessListSpec.grants:type_name -> teleport.accesslist.v1.AccessListGrants - 0, // 7: teleport.accesslist.v1.AccessListOwner.ineligible_status:type_name -> teleport.accesslist.v1.IneligibleStatus - 13, // 8: teleport.accesslist.v1.AccessListAudit.frequency:type_name -> google.protobuf.Duration - 14, // 9: teleport.accesslist.v1.AccessListAudit.next_audit_date:type_name -> google.protobuf.Timestamp - 15, // 10: teleport.accesslist.v1.AccessListRequires.traits:type_name -> teleport.trait.v1.Trait - 15, // 11: teleport.accesslist.v1.AccessListGrants.traits:type_name -> teleport.trait.v1.Trait - 12, // 12: teleport.accesslist.v1.Member.header:type_name -> teleport.header.v1.ResourceHeader - 8, // 13: teleport.accesslist.v1.Member.spec:type_name -> teleport.accesslist.v1.MemberSpec - 14, // 14: teleport.accesslist.v1.MemberSpec.joined:type_name -> google.protobuf.Timestamp - 14, // 15: teleport.accesslist.v1.MemberSpec.expires:type_name -> google.protobuf.Timestamp - 0, // 16: teleport.accesslist.v1.MemberSpec.ineligible_status:type_name -> teleport.accesslist.v1.IneligibleStatus - 12, // 17: teleport.accesslist.v1.Review.header:type_name -> teleport.header.v1.ResourceHeader - 10, // 18: teleport.accesslist.v1.Review.spec:type_name -> teleport.accesslist.v1.ReviewSpec - 14, // 19: teleport.accesslist.v1.ReviewSpec.review_date:type_name -> google.protobuf.Timestamp - 11, // 20: teleport.accesslist.v1.ReviewSpec.changes:type_name -> teleport.accesslist.v1.ReviewChanges - 13, // 21: teleport.accesslist.v1.ReviewChanges.frequency_changed:type_name -> google.protobuf.Duration - 5, // 22: teleport.accesslist.v1.ReviewChanges.membership_requirements_changed:type_name -> teleport.accesslist.v1.AccessListRequires - 23, // [23:23] is the sub-list for method output_type - 23, // [23:23] is the sub-list for method input_type - 23, // [23:23] is the sub-list for extension type_name - 23, // [23:23] is the sub-list for extension extendee - 0, // [0:23] is the sub-list for field type_name + 15, // 0: teleport.accesslist.v1.AccessList.header:type_name -> teleport.header.v1.ResourceHeader + 4, // 1: teleport.accesslist.v1.AccessList.spec:type_name -> teleport.accesslist.v1.AccessListSpec + 5, // 2: teleport.accesslist.v1.AccessListSpec.owners:type_name -> teleport.accesslist.v1.AccessListOwner + 6, // 3: teleport.accesslist.v1.AccessListSpec.audit:type_name -> teleport.accesslist.v1.AccessListAudit + 8, // 4: teleport.accesslist.v1.AccessListSpec.membership_requires:type_name -> teleport.accesslist.v1.AccessListRequires + 8, // 5: teleport.accesslist.v1.AccessListSpec.ownership_requires:type_name -> teleport.accesslist.v1.AccessListRequires + 9, // 6: teleport.accesslist.v1.AccessListSpec.grants:type_name -> teleport.accesslist.v1.AccessListGrants + 2, // 7: teleport.accesslist.v1.AccessListOwner.ineligible_status:type_name -> teleport.accesslist.v1.IneligibleStatus + 16, // 8: teleport.accesslist.v1.AccessListAudit.next_audit_date:type_name -> google.protobuf.Timestamp + 7, // 9: teleport.accesslist.v1.AccessListAudit.recurrence:type_name -> teleport.accesslist.v1.Recurrence + 0, // 10: teleport.accesslist.v1.Recurrence.frequency:type_name -> teleport.accesslist.v1.ReviewFrequency + 1, // 11: teleport.accesslist.v1.Recurrence.day_of_month:type_name -> teleport.accesslist.v1.ReviewDayOfMonth + 17, // 12: teleport.accesslist.v1.AccessListRequires.traits:type_name -> teleport.trait.v1.Trait + 17, // 13: teleport.accesslist.v1.AccessListGrants.traits:type_name -> teleport.trait.v1.Trait + 15, // 14: teleport.accesslist.v1.Member.header:type_name -> teleport.header.v1.ResourceHeader + 11, // 15: teleport.accesslist.v1.Member.spec:type_name -> teleport.accesslist.v1.MemberSpec + 16, // 16: teleport.accesslist.v1.MemberSpec.joined:type_name -> google.protobuf.Timestamp + 16, // 17: teleport.accesslist.v1.MemberSpec.expires:type_name -> google.protobuf.Timestamp + 2, // 18: teleport.accesslist.v1.MemberSpec.ineligible_status:type_name -> teleport.accesslist.v1.IneligibleStatus + 15, // 19: teleport.accesslist.v1.Review.header:type_name -> teleport.header.v1.ResourceHeader + 13, // 20: teleport.accesslist.v1.Review.spec:type_name -> teleport.accesslist.v1.ReviewSpec + 16, // 21: teleport.accesslist.v1.ReviewSpec.review_date:type_name -> google.protobuf.Timestamp + 14, // 22: teleport.accesslist.v1.ReviewSpec.changes:type_name -> teleport.accesslist.v1.ReviewChanges + 8, // 23: teleport.accesslist.v1.ReviewChanges.membership_requirements_changed:type_name -> teleport.accesslist.v1.AccessListRequires + 0, // 24: teleport.accesslist.v1.ReviewChanges.review_frequency_changed:type_name -> teleport.accesslist.v1.ReviewFrequency + 1, // 25: teleport.accesslist.v1.ReviewChanges.review_day_of_month_changed:type_name -> teleport.accesslist.v1.ReviewDayOfMonth + 26, // [26:26] is the sub-list for method output_type + 26, // [26:26] is the sub-list for method input_type + 26, // [26:26] is the sub-list for extension type_name + 26, // [26:26] is the sub-list for extension extendee + 0, // [0:26] is the sub-list for field type_name } func init() { file_teleport_accesslist_v1_accesslist_proto_init() } @@ -1173,7 +1394,7 @@ func file_teleport_accesslist_v1_accesslist_proto_init() { } } file_teleport_accesslist_v1_accesslist_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AccessListRequires); i { + switch v := v.(*Recurrence); i { case 0: return &v.state case 1: @@ -1185,7 +1406,7 @@ func file_teleport_accesslist_v1_accesslist_proto_init() { } } file_teleport_accesslist_v1_accesslist_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AccessListGrants); i { + switch v := v.(*AccessListRequires); i { case 0: return &v.state case 1: @@ -1197,7 +1418,7 @@ func file_teleport_accesslist_v1_accesslist_proto_init() { } } file_teleport_accesslist_v1_accesslist_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Member); i { + switch v := v.(*AccessListGrants); i { case 0: return &v.state case 1: @@ -1209,7 +1430,7 @@ func file_teleport_accesslist_v1_accesslist_proto_init() { } } file_teleport_accesslist_v1_accesslist_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*MemberSpec); i { + switch v := v.(*Member); i { case 0: return &v.state case 1: @@ -1221,7 +1442,7 @@ func file_teleport_accesslist_v1_accesslist_proto_init() { } } file_teleport_accesslist_v1_accesslist_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Review); i { + switch v := v.(*MemberSpec); i { case 0: return &v.state case 1: @@ -1233,7 +1454,7 @@ func file_teleport_accesslist_v1_accesslist_proto_init() { } } file_teleport_accesslist_v1_accesslist_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ReviewSpec); i { + switch v := v.(*Review); i { case 0: return &v.state case 1: @@ -1245,6 +1466,18 @@ func file_teleport_accesslist_v1_accesslist_proto_init() { } } file_teleport_accesslist_v1_accesslist_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ReviewSpec); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_teleport_accesslist_v1_accesslist_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ReviewChanges); i { case 0: return &v.state @@ -1262,8 +1495,8 @@ func file_teleport_accesslist_v1_accesslist_proto_init() { File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_teleport_accesslist_v1_accesslist_proto_rawDesc, - NumEnums: 1, - NumMessages: 11, + NumEnums: 3, + NumMessages: 12, NumExtensions: 0, NumServices: 0, }, diff --git a/api/proto/teleport/accesslist/v1/accesslist.proto b/api/proto/teleport/accesslist/v1/accesslist.proto index fd55aa248d3d..a6d8575b3a90 100644 --- a/api/proto/teleport/accesslist/v1/accesslist.proto +++ b/api/proto/teleport/accesslist/v1/accesslist.proto @@ -16,7 +16,6 @@ syntax = "proto3"; package teleport.accesslist.v1; -import "google/protobuf/duration.proto"; import "google/protobuf/timestamp.proto"; import "teleport/header/v1/resourceheader.proto"; import "teleport/trait/v1/trait.proto"; @@ -80,11 +79,40 @@ message AccessListOwner { // AccessListAudit describes the audit configuration for an access list. message AccessListAudit { - // frequency is a duration that describes how often an access list must be audited. - google.protobuf.Duration frequency = 1; + reserved 1; + reserved "frequency"; // next_audit_date is when the next audit date should be done by. google.protobuf.Timestamp next_audit_date = 2; + + // recurrence is the recurrence definition + Recurrence recurrence = 3; +} + +// ReviewFrequency is the frequency of reviews. +enum ReviewFrequency { + REVIEW_FREQUENCY_UNSPECIFIED = 0; + REVIEW_FREQUENCY_ONE_MONTH = 1; + REVIEW_FREQUENCY_THREE_MONTHS = 3; + REVIEW_FREQUENCY_SIX_MONTHS = 6; + REVIEW_FREQUENCY_ONE_YEAR = 12; +} + +// ReviewDayOfMonth is the day of month that reviews will repeat on. +enum ReviewDayOfMonth { + REVIEW_DAY_OF_MONTH_UNSPECIFIED = 0; + REVIEW_DAY_OF_MONTH_FIRST = 1; + REVIEW_DAY_OF_MONTH_FIFTEENTH = 15; + REVIEW_DAY_OF_MONTH_LAST = 31; +} + +// Recurrence is the definition for when reviews will be scheduled. +message Recurrence { + // frequency is the frequency of reviews. + ReviewFrequency frequency = 1; + + // day_of_month is the day of month that reviews will be scheduled on. + ReviewDayOfMonth day_of_month = 2; } // AccessListRequires describes a requirement section for an access list. A user must @@ -187,12 +215,18 @@ message ReviewSpec { // ReviewChanges are the changes that were made as part of the review. message ReviewChanges { - // audit_frequency_changed is populated if the audit frequency was changed. - google.protobuf.Duration frequency_changed = 1; + reserved 1; + reserved "frequency_changed"; // membership_requirements_changed is populated if the requirements were changed as part of this review. AccessListRequires membership_requirements_changed = 2; // removed_members contains the members that were removed as part of this review. repeated string removed_members = 3; + + // review_frequency_changed is populated if the review frequency has changed. + ReviewFrequency review_frequency_changed = 4; + + // review_day_of_month changed is populated if the review day of month has changed. + ReviewDayOfMonth review_day_of_month_changed = 5; } diff --git a/api/types/accesslist/accesslist.go b/api/types/accesslist/accesslist.go index ace9f67a0c8f..a27f0a8380ba 100644 --- a/api/types/accesslist/accesslist.go +++ b/api/types/accesslist/accesslist.go @@ -18,6 +18,7 @@ package accesslist import ( "encoding/json" + "strings" "time" "github.com/gravitational/trace" @@ -29,6 +30,85 @@ import ( "github.com/gravitational/teleport/api/utils" ) +// ReviewFrequency is the review frequency in months. +type ReviewFrequency int + +const ( + OneMonth ReviewFrequency = 1 + ThreeMonths ReviewFrequency = 3 + SixMonths ReviewFrequency = 6 + OneYear ReviewFrequency = 12 +) + +func (r ReviewFrequency) String() string { + switch r { + case OneMonth: + return "1 month" + case ThreeMonths: + return "3 months" + case SixMonths: + return "6 months" + case OneYear: + return "1 year" + } + + return "" +} + +func parseReviewFrequency(input string) ReviewFrequency { + lowerInput := strings.ReplaceAll(strings.ToLower(input), " ", "") + switch lowerInput { + case "1month", "1months", "1m", "1": + return OneMonth + case "3month", "3months", "3m", "3": + return ThreeMonths + case "6month", "6months", "6m", "6": + return SixMonths + case "12month", "12months", "12m", "12", "1years", "1year", "1y": + return OneYear + } + + // We won't return an error here and we'll just let CheckAndSetDefaults handle the rest. + return 0 +} + +// ReviewDayOfMonth is the day of month the review should be repeated on. +type ReviewDayOfMonth int + +const ( + FirstDayOfMonth ReviewDayOfMonth = 1 + FifteenthDayOfMonth ReviewDayOfMonth = 15 + LastDayOfMonth ReviewDayOfMonth = 31 +) + +func (r ReviewDayOfMonth) String() string { + switch r { + case FirstDayOfMonth: + return "1" + case FifteenthDayOfMonth: + return "15" + case LastDayOfMonth: + return "last" + } + + return "" +} + +func parseReviewDayOfMonth(input string) ReviewDayOfMonth { + lowerInput := strings.ReplaceAll(strings.ToLower(input), " ", "") + switch lowerInput { + case "1", "first": + return FirstDayOfMonth + case "15": + return FifteenthDayOfMonth + case "last": + return LastDayOfMonth + } + + // We won't return an error here and we'll just let CheckAndSetDefaults handle the rest. + return 0 +} + // AccessList describes the basic building block of access grants, which are // similar to access requests but for longer lived permissions that need to be // regularly audited. @@ -82,11 +162,21 @@ type Owner struct { // Audit describes the audit configuration for an access list. type Audit struct { - // Frequency is a duration that describes how often an access list must be audited. - Frequency time.Duration `json:"frequency" yaml:"frequency"` - // NextAuditDate is the date that the next audit should be performed. NextAuditDate time.Time `json:"next_audit_date" yaml:"next_audit_date"` + + // Recurrence is the recurrence definition for auditing. Valid values are + // 1, first, 15, and last. + Recurrence Recurrence `json:"recurrence" yaml:"recurrence"` +} + +// Recurrence defines when access list reviews should occur. +type Recurrence struct { + // Frequency is the frequency between access list reviews. + Frequency ReviewFrequency `json:"frequency" yaml:"frequency"` + + // DayOfMonth is the day of month subsequent reviews will be scheduled on. + DayOfMonth ReviewDayOfMonth `json:"day_of_month" yaml:"day_of_month"` } // Requires describes a requirement section for an access list. A user must @@ -157,11 +247,29 @@ func (a *AccessList) CheckAndSetDefaults() error { return trace.BadParameter("owners are missing") } - if a.Spec.Audit.Frequency == 0 { - return trace.BadParameter("audit frequency must be greater than 0") + if a.Spec.Audit.NextAuditDate.IsZero() { + return trace.BadParameter("next audit date is missing") } - // TODO(mdwn): Next audit date must not be zero. + if a.Spec.Audit.Recurrence.Frequency == 0 { + a.Spec.Audit.Recurrence.Frequency = SixMonths + } + + switch a.Spec.Audit.Recurrence.Frequency { + case OneMonth, ThreeMonths, SixMonths, OneYear: + default: + return trace.BadParameter("recurrence frequency is an invalid value") + } + + if a.Spec.Audit.Recurrence.DayOfMonth == 0 { + a.Spec.Audit.Recurrence.DayOfMonth = FirstDayOfMonth + } + + switch a.Spec.Audit.Recurrence.DayOfMonth { + case FirstDayOfMonth, FifteenthDayOfMonth, LastDayOfMonth: + default: + return trace.BadParameter("recurrence day of month is an invalid value") + } if len(a.Spec.Grants.Roles) == 0 && len(a.Spec.Grants.Traits) == 0 { return trace.BadParameter("grants must specify at least one role or trait") @@ -199,11 +307,6 @@ func (a *AccessList) SetOwners(owners []Owner) { a.Spec.Owners = owners } -// GetAuditFrequency returns the audit frequency from the access list. -func (a *AccessList) GetAuditFrequency() time.Duration { - return a.Spec.Audit.Frequency -} - // GetMembershipRequires returns the membership requires configuration from the access list. func (a *AccessList) GetMembershipRequires() Requires { return a.Spec.MembershipRequires @@ -242,7 +345,6 @@ func (a *AccessList) CloneResource() types.ResourceWithLabels { func (a *Audit) UnmarshalJSON(data []byte) error { type Alias Audit audit := struct { - Frequency string `json:"frequency"` NextAuditDate string `json:"next_audit_date"` *Alias }{ @@ -253,10 +355,6 @@ func (a *Audit) UnmarshalJSON(data []byte) error { } var err error - a.Frequency, err = time.ParseDuration(audit.Frequency) - if err != nil { - return trace.Wrap(err) - } a.NextAuditDate, err = time.Parse(time.RFC3339Nano, audit.NextAuditDate) if err != nil { return trace.Wrap(err) @@ -267,12 +365,41 @@ func (a *Audit) UnmarshalJSON(data []byte) error { func (a Audit) MarshalJSON() ([]byte, error) { type Alias Audit return json.Marshal(&struct { - Frequency string `json:"frequency"` NextAuditDate string `json:"next_audit_date"` Alias }{ Alias: (Alias)(a), - Frequency: a.Frequency.String(), NextAuditDate: a.NextAuditDate.Format(time.RFC3339Nano), }) } + +func (r *Recurrence) UnmarshalJSON(data []byte) error { + type Alias Recurrence + recurrence := struct { + Frequency string `json:"frequency"` + DayOfMonth string `json:"day_of_month"` + *Alias + }{ + Alias: (*Alias)(r), + } + if err := json.Unmarshal(data, &recurrence); err != nil { + return trace.Wrap(err) + } + + r.Frequency = parseReviewFrequency(recurrence.Frequency) + r.DayOfMonth = parseReviewDayOfMonth(recurrence.DayOfMonth) + return nil +} + +func (r Recurrence) MarshalJSON() ([]byte, error) { + type Alias Recurrence + return json.Marshal(&struct { + Frequency string `json:"frequency"` + DayOfMonth string `json:"day_of_month"` + Alias + }{ + Alias: (Alias)(r), + Frequency: r.Frequency.String(), + DayOfMonth: r.DayOfMonth.String(), + }) +} diff --git a/api/types/accesslist/accesslist_test.go b/api/types/accesslist/accesslist_test.go index 4f2e92594a13..5182f6554f11 100644 --- a/api/types/accesslist/accesslist_test.go +++ b/api/types/accesslist/accesslist_test.go @@ -26,6 +26,85 @@ import ( "github.com/gravitational/teleport/api/types/header" ) +func TestParseReviewFrequency(t *testing.T) { + t.Parallel() + + tests := []struct { + input string + expected ReviewFrequency + }{ + {input: "1 month", expected: OneMonth}, + {input: "1month", expected: OneMonth}, + {input: "1months", expected: OneMonth}, + {input: "1 m", expected: OneMonth}, + {input: "1m", expected: OneMonth}, + {input: "1", expected: OneMonth}, + + {input: "3 month", expected: ThreeMonths}, + {input: "3month", expected: ThreeMonths}, + {input: "3months", expected: ThreeMonths}, + {input: "3 m", expected: ThreeMonths}, + {input: "3m", expected: ThreeMonths}, + {input: "3", expected: ThreeMonths}, + + {input: "6 month", expected: SixMonths}, + {input: "6month", expected: SixMonths}, + {input: "6months", expected: SixMonths}, + {input: "6 m", expected: SixMonths}, + {input: "6m", expected: SixMonths}, + {input: "6", expected: SixMonths}, + + {input: "12 month", expected: OneYear}, + {input: "12month", expected: OneYear}, + {input: "12months", expected: OneYear}, + {input: "12 m", expected: OneYear}, + {input: "12m", expected: OneYear}, + {input: "12", expected: OneYear}, + {input: "1 year", expected: OneYear}, + {input: "1year", expected: OneYear}, + {input: "1 y", expected: OneYear}, + {input: "1y", expected: OneYear}, + + {input: "1 MoNtH", expected: OneMonth}, + {input: "unknown"}, + } + + for _, test := range tests { + test := test + t.Run(test.input, func(t *testing.T) { + t.Parallel() + require.Equal(t, test.expected, parseReviewFrequency(test.input)) + }) + } +} + +func TestParseReviewDayOfMonth(t *testing.T) { + t.Parallel() + + tests := []struct { + input string + expected ReviewDayOfMonth + }{ + {input: "1", expected: FirstDayOfMonth}, + {input: "first", expected: FirstDayOfMonth}, + + {input: "15", expected: FifteenthDayOfMonth}, + + {input: "last", expected: LastDayOfMonth}, + + {input: "FiRSt", expected: FirstDayOfMonth}, + {input: "unknown"}, + } + + for _, test := range tests { + test := test + t.Run(test.input, func(t *testing.T) { + t.Parallel() + require.Equal(t, test.expected, parseReviewDayOfMonth(test.input)) + }) + } +} + func TestDeduplicateOwners(t *testing.T) { accessList, err := NewAccessList( header.Metadata{ @@ -49,7 +128,7 @@ func TestDeduplicateOwners(t *testing.T) { }, }, Audit: Audit{ - Frequency: time.Hour, + NextAuditDate: time.Now(), }, MembershipRequires: Requires{ Roles: []string{"mrole1", "mrole2"}, @@ -85,26 +164,26 @@ func TestDeduplicateOwners(t *testing.T) { func TestAuditMarshaling(t *testing.T) { audit := Audit{ - Frequency: time.Hour, NextAuditDate: time.Date(2023, 02, 02, 0, 0, 0, 0, time.UTC), + Recurrence: Recurrence{ + Frequency: SixMonths, + DayOfMonth: LastDayOfMonth, + }, } data, err := json.Marshal(&audit) require.NoError(t, err) - require.Equal(t, `{"frequency":"1h0m0s","next_audit_date":"2023-02-02T00:00:00Z"}`, string(data)) - - raw := map[string]interface{}{} - require.NoError(t, json.Unmarshal(data, &raw)) - - require.Equal(t, "1h0m0s", raw["frequency"]) - require.Equal(t, "2023-02-02T00:00:00Z", raw["next_audit_date"]) + require.Equal(t, `{"next_audit_date":"2023-02-02T00:00:00Z","recurrence":{"frequency":"6 months","day_of_month":"last"}}`, string(data)) } func TestAuditUnmarshaling(t *testing.T) { raw := map[string]interface{}{ - "frequency": "1h", "next_audit_date": "2023-02-02T00:00:00Z", + "recurrence": map[string]interface{}{ + "frequency": "3 months", + "day_of_month": "1", + }, } data, err := json.Marshal(&raw) @@ -113,6 +192,7 @@ func TestAuditUnmarshaling(t *testing.T) { var audit Audit require.NoError(t, json.Unmarshal(data, &audit)) - require.Equal(t, time.Hour, audit.Frequency) require.Equal(t, time.Date(2023, 02, 02, 0, 0, 0, 0, time.UTC), audit.NextAuditDate) + require.Equal(t, ThreeMonths, audit.Recurrence.Frequency) + require.Equal(t, FirstDayOfMonth, audit.Recurrence.DayOfMonth) } diff --git a/api/types/accesslist/convert/v1/accesslist.go b/api/types/accesslist/convert/v1/accesslist.go index e652d5a4632c..70f88ee65252 100644 --- a/api/types/accesslist/convert/v1/accesslist.go +++ b/api/types/accesslist/convert/v1/accesslist.go @@ -18,7 +18,6 @@ package v1 import ( "github.com/gravitational/trace" - "google.golang.org/protobuf/types/known/durationpb" "google.golang.org/protobuf/types/known/timestamppb" accesslistv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/accesslist/v1" @@ -51,6 +50,12 @@ func FromProto(msg *accesslistv1.AccessList, opts ...AccessListOption) (*accessl return nil, trace.BadParameter("grants is missing") } + var recurrence accesslist.Recurrence + if msg.Spec.Audit.Recurrence != nil { + recurrence.Frequency = accesslist.ReviewFrequency(msg.Spec.Audit.Recurrence.Frequency) + recurrence.DayOfMonth = accesslist.ReviewDayOfMonth(msg.Spec.Audit.Recurrence.DayOfMonth) + } + owners := make([]accesslist.Owner, len(msg.Spec.Owners)) for i, owner := range msg.Spec.Owners { owners[i] = accesslist.Owner{ @@ -67,8 +72,8 @@ func FromProto(msg *accesslistv1.AccessList, opts ...AccessListOption) (*accessl Description: msg.Spec.Description, Owners: owners, Audit: accesslist.Audit{ - Frequency: msg.Spec.Audit.Frequency.AsDuration(), NextAuditDate: msg.Spec.Audit.NextAuditDate.AsTime(), + Recurrence: recurrence, }, MembershipRequires: accesslist.Requires{ Roles: msg.Spec.MembershipRequires.Roles, @@ -116,8 +121,11 @@ func ToProto(accessList *accesslist.AccessList) *accesslistv1.AccessList { Description: accessList.Spec.Description, Owners: owners, Audit: &accesslistv1.AccessListAudit{ - Frequency: durationpb.New(accessList.Spec.Audit.Frequency), NextAuditDate: timestamppb.New(accessList.Spec.Audit.NextAuditDate), + Recurrence: &accesslistv1.Recurrence{ + Frequency: accesslistv1.ReviewFrequency(accessList.Spec.Audit.Recurrence.Frequency), + DayOfMonth: accesslistv1.ReviewDayOfMonth(accessList.Spec.Audit.Recurrence.DayOfMonth), + }, }, MembershipRequires: &accesslistv1.AccessListRequires{ Roles: accessList.Spec.MembershipRequires.Roles, diff --git a/api/types/accesslist/convert/v1/accesslist_test.go b/api/types/accesslist/convert/v1/accesslist_test.go index 6c3a9d226358..a900d3689174 100644 --- a/api/types/accesslist/convert/v1/accesslist_test.go +++ b/api/types/accesslist/convert/v1/accesslist_test.go @@ -150,7 +150,6 @@ func newAccessList(t *testing.T, name string) *accesslist.AccessList { }, }, Audit: accesslist.Audit{ - Frequency: time.Hour, NextAuditDate: time.Now(), }, MembershipRequires: accesslist.Requires{ diff --git a/api/types/accesslist/convert/v1/review.go b/api/types/accesslist/convert/v1/review.go index fe046abd5c09..42bb7ebfc891 100644 --- a/api/types/accesslist/convert/v1/review.go +++ b/api/types/accesslist/convert/v1/review.go @@ -20,7 +20,6 @@ import ( "time" "github.com/gravitational/trace" - "google.golang.org/protobuf/types/known/durationpb" "google.golang.org/protobuf/types/known/timestamppb" accesslistv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/accesslist/v1" @@ -48,9 +47,6 @@ func FromReviewProto(msg *accesslistv1.Review) (*accesslist.Review, error) { var reviewChanges accesslist.ReviewChanges if msg.Spec.Changes != nil { - if msg.Spec.Changes.FrequencyChanged != nil { - reviewChanges.FrequencyChanged = msg.Spec.Changes.FrequencyChanged.AsDuration() - } if msg.Spec.Changes.MembershipRequirementsChanged != nil { reviewChanges.MembershipRequirementsChanged = &accesslist.Requires{ Roles: msg.Spec.Changes.MembershipRequirementsChanged.Roles, @@ -58,6 +54,8 @@ func FromReviewProto(msg *accesslistv1.Review) (*accesslist.Review, error) { } } reviewChanges.RemovedMembers = msg.Spec.Changes.RemovedMembers + reviewChanges.ReviewFrequencyChanged = accesslist.ReviewFrequency(msg.Spec.Changes.ReviewFrequencyChanged) + reviewChanges.ReviewDayOfMonthChanged = accesslist.ReviewDayOfMonth(msg.Spec.Changes.ReviewDayOfMonthChanged) } member, err := accesslist.NewReview(headerv1.FromMetadataProto(msg.Header.Metadata), accesslist.ReviewSpec{ @@ -77,11 +75,6 @@ func FromReviewProto(msg *accesslistv1.Review) (*accesslist.Review, error) { // ToReviewProto converts an internal access list review into a v1 access list review object. func ToReviewProto(review *accesslist.Review) *accesslistv1.Review { var reviewChanges *accesslistv1.ReviewChanges - if review.Spec.Changes.FrequencyChanged > 0 { - reviewChanges = &accesslistv1.ReviewChanges{ - FrequencyChanged: durationpb.New(review.Spec.Changes.FrequencyChanged), - } - } if review.Spec.Changes.MembershipRequirementsChanged != nil { if reviewChanges == nil { reviewChanges = &accesslistv1.ReviewChanges{} @@ -99,6 +92,20 @@ func ToReviewProto(review *accesslist.Review) *accesslistv1.Review { reviewChanges.RemovedMembers = review.Spec.Changes.RemovedMembers } + if review.Spec.Changes.ReviewFrequencyChanged > 0 { + if reviewChanges == nil { + reviewChanges = &accesslistv1.ReviewChanges{} + } + + reviewChanges.ReviewFrequencyChanged = accesslistv1.ReviewFrequency(review.Spec.Changes.ReviewFrequencyChanged) + } + if review.Spec.Changes.ReviewDayOfMonthChanged > 0 { + if reviewChanges == nil { + reviewChanges = &accesslistv1.ReviewChanges{} + } + + reviewChanges.ReviewDayOfMonthChanged = accesslistv1.ReviewDayOfMonth(review.Spec.Changes.ReviewDayOfMonthChanged) + } return &accesslistv1.Review{ Header: headerv1.ToResourceHeaderProto(review.ResourceHeader), diff --git a/api/types/accesslist/convert/v1/review_test.go b/api/types/accesslist/convert/v1/review_test.go index c9f58bf0a94c..e31884cbd9a1 100644 --- a/api/types/accesslist/convert/v1/review_test.go +++ b/api/types/accesslist/convert/v1/review_test.go @@ -23,6 +23,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/stretchr/testify/require" + accesslistv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/accesslist/v1" "github.com/gravitational/teleport/api/types/accesslist" "github.com/gravitational/teleport/api/types/header" "github.com/gravitational/teleport/api/types/trait" @@ -90,10 +91,6 @@ func TestReviewFromProtoNils(t *testing.T) { _, err = FromReviewProto(review) require.NoError(t, err) - // FrequencyChanged is nil - review = ToReviewProto(newAccessListReview(t, "access-list-review")) - review.Spec.Changes.FrequencyChanged = nil - _, err = FromReviewProto(review) require.NoError(t, err) @@ -110,6 +107,20 @@ func TestReviewFromProtoNils(t *testing.T) { _, err = FromReviewProto(review) require.NoError(t, err) + + // ReviewFrequencyChanged is nil + review = ToReviewProto(newAccessListReview(t, "access-list-review")) + review.Spec.Changes.ReviewFrequencyChanged = 0 + + _, err = FromReviewProto(review) + require.NoError(t, err) + + // ReviewFrequencyDayOfMonth is nil + review = ToReviewProto(newAccessListReview(t, "access-list-review")) + review.Spec.Changes.ReviewDayOfMonthChanged = 0 + + _, err = FromReviewProto(review) + require.NoError(t, err) } func TestReviewToProtoChanges(t *testing.T) { @@ -117,43 +128,62 @@ func TestReviewToProtoChanges(t *testing.T) { // No changes. review := newAccessListReview(t, "access-list-review") - review.Spec.Changes.FrequencyChanged = 0 review.Spec.Changes.MembershipRequirementsChanged = nil review.Spec.Changes.RemovedMembers = nil + review.Spec.Changes.ReviewFrequencyChanged = 0 + review.Spec.Changes.ReviewDayOfMonthChanged = 0 msg := ToReviewProto(review) require.Nil(t, msg.Spec.Changes) - // Only frequency changes. + // Only membership requires changes. review = newAccessListReview(t, "access-list-review") - review.Spec.Changes.MembershipRequirementsChanged = nil review.Spec.Changes.RemovedMembers = nil + review.Spec.Changes.ReviewFrequencyChanged = 0 + review.Spec.Changes.ReviewDayOfMonthChanged = 0 msg = ToReviewProto(review) - require.Equal(t, review.Spec.Changes.FrequencyChanged, msg.Spec.Changes.FrequencyChanged.AsDuration()) - require.Nil(t, msg.Spec.Changes.MembershipRequirementsChanged) + require.Equal(t, review.Spec.Changes.MembershipRequirementsChanged.Roles, msg.Spec.Changes.MembershipRequirementsChanged.Roles) + require.Equal(t, review.Spec.Changes.MembershipRequirementsChanged.Traits, traitv1.FromProto(msg.Spec.Changes.MembershipRequirementsChanged.Traits)) require.Nil(t, msg.Spec.Changes.RemovedMembers) + require.Zero(t, msg.Spec.Changes.ReviewFrequencyChanged) + require.Zero(t, msg.Spec.Changes.ReviewDayOfMonthChanged) - // Only membership requires changes. + // Only removed members changes. + review = newAccessListReview(t, "access-list-review") + review.Spec.Changes.MembershipRequirementsChanged = nil + review.Spec.Changes.ReviewFrequencyChanged = 0 + review.Spec.Changes.ReviewDayOfMonthChanged = 0 + + msg = ToReviewProto(review) + require.Nil(t, msg.Spec.Changes.MembershipRequirementsChanged) + require.Equal(t, review.Spec.Changes.RemovedMembers, msg.Spec.Changes.RemovedMembers) + require.Zero(t, msg.Spec.Changes.ReviewFrequencyChanged) + require.Zero(t, msg.Spec.Changes.ReviewDayOfMonthChanged) + + // Only review frequency changes. review = newAccessListReview(t, "access-list-review") - review.Spec.Changes.FrequencyChanged = 0 + review.Spec.Changes.MembershipRequirementsChanged = nil review.Spec.Changes.RemovedMembers = nil + review.Spec.Changes.ReviewDayOfMonthChanged = 0 msg = ToReviewProto(review) - require.Equal(t, time.Duration(0), review.Spec.Changes.FrequencyChanged) - require.Equal(t, review.Spec.Changes.MembershipRequirementsChanged.Roles, msg.Spec.Changes.MembershipRequirementsChanged.Roles) - require.Equal(t, review.Spec.Changes.MembershipRequirementsChanged.Traits, traitv1.FromProto(msg.Spec.Changes.MembershipRequirementsChanged.Traits)) - require.Nil(t, msg.Spec.Changes.RemovedMembers) + require.Nil(t, msg.Spec.Changes.MembershipRequirementsChanged) + require.Equal(t, review.Spec.Changes.RemovedMembers, msg.Spec.Changes.RemovedMembers) + require.Equal(t, accesslistv1.ReviewFrequency_REVIEW_FREQUENCY_THREE_MONTHS, msg.Spec.Changes.ReviewFrequencyChanged) + require.Zero(t, msg.Spec.Changes.ReviewDayOfMonthChanged) - // Only removed members changes. + // Only review day of month changes. review = newAccessListReview(t, "access-list-review") - review.Spec.Changes.FrequencyChanged = 0 review.Spec.Changes.MembershipRequirementsChanged = nil + review.Spec.Changes.RemovedMembers = nil + review.Spec.Changes.ReviewFrequencyChanged = 0 msg = ToReviewProto(review) - require.Equal(t, time.Duration(0), review.Spec.Changes.FrequencyChanged) require.Nil(t, msg.Spec.Changes.MembershipRequirementsChanged) require.Equal(t, review.Spec.Changes.RemovedMembers, msg.Spec.Changes.RemovedMembers) + require.Zero(t, msg.Spec.Changes.ReviewFrequencyChanged) + require.Equal(t, accesslistv1.ReviewDayOfMonth_REVIEW_DAY_OF_MONTH_FIFTEENTH, msg.Spec.Changes.ReviewDayOfMonthChanged) } func newAccessListReview(t *testing.T, name string) *accesslist.Review { @@ -172,7 +202,6 @@ func newAccessListReview(t *testing.T, name string) *accesslist.Review { ReviewDate: time.Date(2023, 01, 01, 0, 0, 0, 0, time.UTC), Notes: "some notes", Changes: accesslist.ReviewChanges{ - FrequencyChanged: 20 * time.Hour, MembershipRequirementsChanged: &accesslist.Requires{ Roles: []string{"role1", "role2"}, Traits: trait.Traits{ @@ -185,6 +214,8 @@ func newAccessListReview(t *testing.T, name string) *accesslist.Review { "removed2", "removed3", }, + ReviewFrequencyChanged: accesslist.ThreeMonths, + ReviewDayOfMonthChanged: accesslist.FifteenthDayOfMonth, }, }, ) diff --git a/api/types/accesslist/review.go b/api/types/accesslist/review.go index 47f5f8ba9df9..6df77afa4868 100644 --- a/api/types/accesslist/review.go +++ b/api/types/accesslist/review.go @@ -57,14 +57,17 @@ type ReviewSpec struct { // ReviewChanges are the changes that were made as part of the review. type ReviewChanges struct { - // FrequencyChanged is populated if the audit frequency was changed. - FrequencyChanged time.Duration `json:"frequency_changed" yaml:"frequency_changed"` - // MembershipRequirementsChanged is populated if the requirements were changed as part of this review. MembershipRequirementsChanged *Requires `json:"membership_requirements_changed" yaml:"membership_requirements_changed"` // RemovedMembers contains the members that were removed as part of this review. RemovedMembers []string `json:"removed_members" yaml:"removed_members"` + + // ReviewFrequencyChanged is populated if the review frequency has changed. + ReviewFrequencyChanged ReviewFrequency `json:"review_frequency_changed" yaml:"review_frequency_changed"` + + // ReviewDayOfMonthChanged changed is populated if the review day of month has changed. + ReviewDayOfMonthChanged ReviewDayOfMonth `json:"review_day_of_month_changed" yaml:"review_day_of_month_changed"` } // NewReview will create a new access list review. @@ -145,7 +148,8 @@ func (r ReviewSpec) MarshalJSON() ([]byte, error) { func (r *ReviewChanges) UnmarshalJSON(data []byte) error { type Alias ReviewChanges review := struct { - FrequencyChanged string `json:"frequency_changed"` + ReviewFrequencyChanged string `json:"review_frequency_changed"` + ReviewDayOfMonthChanged string `json:"review_day_of_month_changed"` *Alias }{ Alias: (*Alias)(r), @@ -153,22 +157,20 @@ func (r *ReviewChanges) UnmarshalJSON(data []byte) error { if err := json.Unmarshal(data, &review); err != nil { return trace.Wrap(err) } - - var err error - r.FrequencyChanged, err = time.ParseDuration(review.FrequencyChanged) - if err != nil { - return trace.Wrap(err) - } + r.ReviewFrequencyChanged = parseReviewFrequency(review.ReviewFrequencyChanged) + r.ReviewDayOfMonthChanged = parseReviewDayOfMonth(review.ReviewDayOfMonthChanged) return nil } func (r ReviewChanges) MarshalJSON() ([]byte, error) { type Alias ReviewChanges return json.Marshal(&struct { - FrequencyChanged string `json:"frequency_changed"` + ReviewFrequencyChanged string `json:"review_frequency_changed"` + ReviewDayOfMonthChanged string `json:"review_day_of_month_changed"` Alias }{ - Alias: (Alias)(r), - FrequencyChanged: r.FrequencyChanged.String(), + Alias: (Alias)(r), + ReviewFrequencyChanged: r.ReviewFrequencyChanged.String(), + ReviewDayOfMonthChanged: r.ReviewDayOfMonthChanged.String(), }) } diff --git a/api/types/accesslist/review_test.go b/api/types/accesslist/review_test.go index a1d29ac54452..3c28fb0996b7 100644 --- a/api/types/accesslist/review_test.go +++ b/api/types/accesslist/review_test.go @@ -36,7 +36,6 @@ func TestReviewSpecMarshaling(t *testing.T) { ReviewDate: time.Date(2023, 01, 01, 0, 0, 0, 0, time.UTC), Notes: "Some notes", Changes: ReviewChanges{ - FrequencyChanged: 300 * time.Second, MembershipRequirementsChanged: &Requires{ Roles: []string{ "member1", @@ -57,6 +56,8 @@ func TestReviewSpecMarshaling(t *testing.T) { "member1", "member2", }, + ReviewFrequencyChanged: SixMonths, + ReviewDayOfMonthChanged: FirstDayOfMonth, }, } @@ -64,15 +65,16 @@ func TestReviewSpecMarshaling(t *testing.T) { require.NoError(t, err) require.Equal(t, `{"review_date":"2023-01-01T00:00:00Z","access_list":"access-list","reviewers":["user1","user2"],`+ - `"notes":"Some notes","changes":{"frequency_changed":"5m0s","membership_requirements_changed":`+ - `{"roles":["member1","member2"],"traits":{"trait1":["value1","value2"],"trait2":["value1","value2"]}},`+ + `"notes":"Some notes","changes":{"review_frequency_changed":"6 months","review_day_of_month_changed":"1",`+ + `"membership_requirements_changed":{"roles":["member1","member2"],"traits":{"trait1":["value1","value2"],"trait2":["value1","value2"]}},`+ `"removed_members":["member1","member2"]}}`, string(data)) raw := map[string]interface{}{} require.NoError(t, json.Unmarshal(data, &raw)) require.Equal(t, "2023-01-01T00:00:00Z", raw["review_date"]) - require.Equal(t, "5m0s", raw["changes"].(map[string]interface{})["frequency_changed"]) + require.Equal(t, SixMonths.String(), raw["changes"].(map[string]interface{})["review_frequency_changed"]) + require.Equal(t, FirstDayOfMonth.String(), raw["changes"].(map[string]interface{})["review_day_of_month_changed"]) } func TestReviewSpecUnmarshaling(t *testing.T) { @@ -85,7 +87,6 @@ func TestReviewSpecUnmarshaling(t *testing.T) { "review_date": "2023-01-01T00:00:00Z", "notes": "Some notes", "changes": map[string]interface{}{ - "frequency_changed": "5m0s", "membership_requirements_changed": map[string]interface{}{ "roles": []string{ "member1", @@ -106,6 +107,8 @@ func TestReviewSpecUnmarshaling(t *testing.T) { "member1", "member2", }, + "review_frequency_changed": "1 month", + "review_day_of_month_changed": "1", }, } @@ -116,5 +119,6 @@ func TestReviewSpecUnmarshaling(t *testing.T) { require.NoError(t, json.Unmarshal(data, &reviewSpec)) require.Equal(t, time.Date(2023, 01, 01, 0, 0, 0, 0, time.UTC), reviewSpec.ReviewDate) - require.Equal(t, 300*time.Second, reviewSpec.Changes.FrequencyChanged) + require.Equal(t, OneMonth, reviewSpec.Changes.ReviewFrequencyChanged) + require.Equal(t, FirstDayOfMonth, reviewSpec.Changes.ReviewDayOfMonthChanged) } diff --git a/lib/auth/userloginstate/generator_test.go b/lib/auth/userloginstate/generator_test.go index a20819dd21f4..af077e7aabd6 100644 --- a/lib/auth/userloginstate/generator_test.go +++ b/lib/auth/userloginstate/generator_test.go @@ -365,7 +365,6 @@ func newAccessList(t *testing.T, clock clockwork.Clock, name string, roles []str }, accesslist.Spec{ Title: "title", Audit: accesslist.Audit{ - Frequency: time.Hour * 8760, // Roughly a year NextAuditDate: clock.Now().Add(time.Hour * 48), }, Owners: []accesslist.Owner{ diff --git a/lib/cache/cache_test.go b/lib/cache/cache_test.go index 12262e7be8b0..a816321aab57 100644 --- a/lib/cache/cache_test.go +++ b/lib/cache/cache_test.go @@ -2094,7 +2094,7 @@ func TestAccessLists(t *testing.T) { testResources(t, p, testFuncs[*accesslist.AccessList]{ newResource: func(name string) (*accesslist.AccessList, error) { - return newAccessList(t, name), nil + return newAccessList(t, name, p.backend.Clock()), nil }, create: func(ctx context.Context, accessList *accesslist.AccessList) error { _, err := p.accessLists.UpsertAccessList(ctx, accessList) @@ -2148,7 +2148,9 @@ func TestAccessListMembers(t *testing.T) { const accessListName = "test-access-list" - p.accessLists.UpsertAccessList(context.Background(), newAccessList(t, accessListName)) + clock := clockwork.NewFakeClock() + + p.accessLists.UpsertAccessList(context.Background(), newAccessList(t, accessListName, clock)) testResources(t, p, testFuncs[*accesslist.AccessListMember]{ newResource: func(name string) (*accesslist.AccessListMember, error) { @@ -2575,6 +2577,8 @@ func newProxyEvents(events types.Events, ignoreKinds []types.WatchKind) *proxyEv func TestCacheWatchKindExistsInEvents(t *testing.T) { t.Parallel() + clock := clockwork.NewFakeClock() + cases := map[string]Config{ "ForAuth": ForAuth(Config{}), "ForProxy": ForProxy(Config{}), @@ -2630,7 +2634,7 @@ func TestCacheWatchKindExistsInEvents(t *testing.T) { types.KindOktaAssignment: &types.OktaAssignmentV1{}, types.KindIntegration: &types.IntegrationV1{}, types.KindHeadlessAuthentication: &types.HeadlessAuthentication{}, - types.KindAccessList: newAccessList(t, "access-list"), + types.KindAccessList: newAccessList(t, "access-list", clock), types.KindUserLoginState: newUserLoginState(t, "user-login-state"), types.KindAccessListMember: newAccessListMember(t, "access-list", "member"), } @@ -2833,7 +2837,7 @@ func TestInvalidDatabases(t *testing.T) { } } -func newAccessList(t *testing.T, name string) *accesslist.AccessList { +func newAccessList(t *testing.T, name string, clock clockwork.Clock) *accesslist.AccessList { t.Helper() accessList, err := accesslist.NewAccessList( @@ -2854,7 +2858,7 @@ func newAccessList(t *testing.T, name string) *accesslist.AccessList { }, }, Audit: accesslist.Audit{ - Frequency: time.Hour, + NextAuditDate: clock.Now(), }, MembershipRequires: accesslist.Requires{ Roles: []string{"mrole1", "mrole2"}, diff --git a/lib/services/access_list.go b/lib/services/access_list.go index f82e986590e8..c663e23ad2c1 100644 --- a/lib/services/access_list.go +++ b/lib/services/access_list.go @@ -18,6 +18,7 @@ package services import ( "context" + "time" "github.com/gravitational/trace" "github.com/jonboulle/clockwork" @@ -277,3 +278,22 @@ func UserMeetsRequirements(identity tlsca.Identity, requires accesslist.Requires // The user meets all requirements. return true } + +// SelectNextReviewDate will select the next review date for the access list. +func SelectNextReviewDate(accessList *accesslist.AccessList) time.Time { + numMonths := int(accessList.Spec.Audit.Recurrence.Frequency) + dayOfMonth := int(accessList.Spec.Audit.Recurrence.DayOfMonth) + + // If the last day of the month has been specified, use the 0 day of the + // next month, which will result in the last day of the target month. + if dayOfMonth == int(accesslist.LastDayOfMonth) { + numMonths += 1 + dayOfMonth = 0 + } + + currentReviewDate := accessList.Spec.Audit.NextAuditDate + nextDate := time.Date(currentReviewDate.Year(), currentReviewDate.Month()+time.Month(numMonths), dayOfMonth, + 0, 0, 0, 0, time.UTC) + + return nextDate +} diff --git a/lib/services/access_list_test.go b/lib/services/access_list_test.go index 68186dc22000..5d8e7080f0e5 100644 --- a/lib/services/access_list_test.go +++ b/lib/services/access_list_test.go @@ -59,7 +59,6 @@ func TestAccessListUnmarshal(t *testing.T) { }, }, Audit: accesslist.Audit{ - Frequency: time.Hour, NextAuditDate: time.Date(2023, 02, 02, 0, 0, 0, 0, time.UTC), }, MembershipRequires: accesslist.Requires{ @@ -113,7 +112,6 @@ func TestAccessListMarshal(t *testing.T) { }, }, Audit: accesslist.Audit{ - Frequency: time.Hour, NextAuditDate: time.Date(2023, 02, 02, 0, 0, 0, 0, time.UTC), }, MembershipRequires: accesslist.Requires{ @@ -415,6 +413,61 @@ func TestIsAccessListMember(t *testing.T) { } } +func TestSelectNextReviewDate(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + frequency accesslist.ReviewFrequency + dayOfMonth accesslist.ReviewDayOfMonth + currentReviewDate time.Time + expected time.Time + }{ + { + name: "one month, first day", + frequency: accesslist.OneMonth, + dayOfMonth: accesslist.FirstDayOfMonth, + currentReviewDate: time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC), + expected: time.Date(2023, 2, 1, 0, 0, 0, 0, time.UTC), + }, + { + name: "one month, fifteenth day", + frequency: accesslist.OneMonth, + dayOfMonth: accesslist.FifteenthDayOfMonth, + currentReviewDate: time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC), + expected: time.Date(2023, 2, 15, 0, 0, 0, 0, time.UTC), + }, + { + name: "one month, last day", + frequency: accesslist.OneMonth, + dayOfMonth: accesslist.LastDayOfMonth, + currentReviewDate: time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC), + expected: time.Date(2023, 2, 28, 0, 0, 0, 0, time.UTC), + }, + { + name: "six months, last day", + frequency: accesslist.SixMonths, + dayOfMonth: accesslist.LastDayOfMonth, + currentReviewDate: time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC), + expected: time.Date(2023, 7, 31, 0, 0, 0, 0, time.UTC), + }, + } + + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + t.Parallel() + accessList := newAccessList(t) + accessList.Spec.Audit.NextAuditDate = test.currentReviewDate + accessList.Spec.Audit.Recurrence = accesslist.Recurrence{ + Frequency: test.frequency, + DayOfMonth: test.dayOfMonth, + } + require.Equal(t, test.expected, SelectNextReviewDate(accessList)) + }) + } +} + func newAccessList(t *testing.T) *accesslist.AccessList { t.Helper() @@ -436,7 +489,6 @@ func newAccessList(t *testing.T) *accesslist.AccessList { }, }, Audit: accesslist.Audit{ - Frequency: time.Hour, NextAuditDate: time.Date(2024, 6, 1, 0, 0, 0, 0, time.UTC), }, MembershipRequires: accesslist.Requires{ diff --git a/lib/services/local/access_list_test.go b/lib/services/local/access_list_test.go index 135a26b6dc8e..1f72ef6652b6 100644 --- a/lib/services/local/access_list_test.go +++ b/lib/services/local/access_list_test.go @@ -48,8 +48,8 @@ func TestAccessListCRUD(t *testing.T) { require.NoError(t, err) // Create a couple access lists. - accessList1 := newAccessList(t, "accessList1") - accessList2 := newAccessList(t, "accessList2") + accessList1 := newAccessList(t, "accessList1", clock) + accessList2 := newAccessList(t, "accessList2", clock) // Initially we expect no access lists. out, err := service.GetAccessLists(ctx) @@ -126,7 +126,7 @@ func TestAccessListCRUD(t *testing.T) { require.Empty(t, out) // Try to create an access list with duplicate owners. - accessListDuplicateOwners := newAccessList(t, "accessListDuplicateOwners") + accessListDuplicateOwners := newAccessList(t, "accessListDuplicateOwners", clock) accessListDuplicateOwners.Spec.Owners = append(accessListDuplicateOwners.Spec.Owners, accessListDuplicateOwners.Spec.Owners[0]) _, err = service.UpsertAccessList(ctx, accessListDuplicateOwners) @@ -147,7 +147,7 @@ func TestAccessListDedupeOwnersBackwardsCompat(t *testing.T) { require.NoError(t, err) // Put an unduplicated owners access list in the backend. - accessListDuplicateOwners := newAccessList(t, "accessListDuplicateOwners") + accessListDuplicateOwners := newAccessList(t, "accessListDuplicateOwners", clock) accessListDuplicateOwners.Spec.Owners = append(accessListDuplicateOwners.Spec.Owners, accessListDuplicateOwners.Spec.Owners[0]) require.Len(t, accessListDuplicateOwners.Spec.Owners, 3) @@ -176,7 +176,7 @@ func TestAccessListUpsertWithMembers(t *testing.T) { require.NoError(t, err) // Create a couple access lists. - accessList1 := newAccessList(t, "accessList1") + accessList1 := newAccessList(t, "accessList1", clock) cmpOpts := []cmp.Option{ cmpopts.IgnoreFields(header.Metadata{}, "ID", "Revision"), @@ -252,8 +252,8 @@ func TestAccessListMembersCRUD(t *testing.T) { require.NoError(t, err) // Create a couple access lists. - accessList1 := newAccessList(t, "accessList1") - accessList2 := newAccessList(t, "accessList2") + accessList1 := newAccessList(t, "accessList1", clock) + accessList2 := newAccessList(t, "accessList2", clock) cmpOpts := []cmp.Option{ cmpopts.IgnoreFields(header.Metadata{}, "ID", "Revision"), @@ -403,7 +403,7 @@ func TestAccessListMembersCRUD(t *testing.T) { require.ErrorIs(t, err, trace.NotFound("access_list %q doesn't exist", accessList2.GetName())) } -func newAccessList(t *testing.T, name string) *accesslist.AccessList { +func newAccessList(t *testing.T, name string, clock clockwork.Clock) *accesslist.AccessList { t.Helper() accessList, err := accesslist.NewAccessList( @@ -424,7 +424,7 @@ func newAccessList(t *testing.T, name string) *accesslist.AccessList { }, }, Audit: accesslist.Audit{ - Frequency: time.Hour, + NextAuditDate: clock.Now(), }, MembershipRequires: accesslist.Requires{ Roles: []string{"mrole1", "mrole2"}, diff --git a/lib/services/unified_resource_test.go b/lib/services/unified_resource_test.go index d60bf34df4cb..ffae9659b853 100644 --- a/lib/services/unified_resource_test.go +++ b/lib/services/unified_resource_test.go @@ -158,7 +158,7 @@ func TestUnifiedResourceWatcher(t *testing.T) { }, }, Audit: accesslist.Audit{ - Frequency: time.Hour, + NextAuditDate: time.Now(), }, MembershipRequires: accesslist.Requires{ Roles: []string{"mrole1", "mrole2"}, diff --git a/tool/tctl/common/acl_command.go b/tool/tctl/common/acl_command.go index 1128331594d0..08d242cb613b 100644 --- a/tool/tctl/common/acl_command.go +++ b/tool/tctl/common/acl_command.go @@ -232,7 +232,7 @@ func displayAccessLists(format string, accessLists ...*accesslist.AccessList) er } func displayAccessListsText(accessLists ...*accesslist.AccessList) error { - table := asciitable.MakeTable([]string{"ID", "Audit Frequency", "Granted Roles", "Granted Traits"}) + table := asciitable.MakeTable([]string{"ID", "Review Frequency", "Review Day Of Month", "Granted Roles", "Granted Traits"}) for _, accessList := range accessLists { grantedRoles := strings.Join(accessList.GetGrants().Roles, ",") traitStrings := make([]string, 0, len(accessList.GetGrants().Traits)) @@ -242,7 +242,8 @@ func displayAccessListsText(accessLists ...*accesslist.AccessList) error { grantedTraits := strings.Join(traitStrings, ",") table.AddRow([]string{ accessList.GetName(), - accessList.GetAuditFrequency().String(), + accessList.Spec.Audit.Recurrence.Frequency.String(), + accessList.Spec.Audit.Recurrence.DayOfMonth.String(), grantedRoles, grantedTraits, })