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, })