diff --git a/api/types/accesslist/accesslist.go b/api/types/accesslist/accesslist.go index 7fe01194c3e32..dfc6c239bc90d 100644 --- a/api/types/accesslist/accesslist.go +++ b/api/types/accesslist/accesslist.go @@ -450,6 +450,9 @@ func (a *Audit) UnmarshalJSON(data []byte) error { return trace.Wrap(err) } + if audit.NextAuditDate == "" { + return nil + } var err error a.NextAuditDate, err = time.Parse(time.RFC3339Nano, audit.NextAuditDate) if err != nil { diff --git a/api/types/accesslist/accesslist_test.go b/api/types/accesslist/accesslist_test.go index 64c1498309e55..6a902754d031a 100644 --- a/api/types/accesslist/accesslist_test.go +++ b/api/types/accesslist/accesslist_test.go @@ -182,27 +182,65 @@ func TestAuditMarshaling(t *testing.T) { } func TestAuditUnmarshaling(t *testing.T) { - raw := map[string]interface{}{ - "next_audit_date": "2023-02-02T00:00:00Z", - "recurrence": map[string]interface{}{ - "frequency": "3 months", - "day_of_month": "1", + tests := []struct { + name string + input map[string]interface{} + expectedNextAudit time.Time + expectedRecurrence Recurrence + expectedNotificationStart time.Duration + }{ + { + name: "with next_audit_date", + input: map[string]interface{}{ + "next_audit_date": "2023-02-02T00:00:00Z", + "recurrence": map[string]interface{}{ + "frequency": "3 months", + "day_of_month": "1", + }, + "notifications": map[string]interface{}{ + "start": twoWeeks.String(), + }, + }, + expectedNextAudit: time.Date(2023, 02, 02, 0, 0, 0, 0, time.UTC), + expectedRecurrence: Recurrence{ + Frequency: ThreeMonths, + DayOfMonth: FirstDayOfMonth, + }, + expectedNotificationStart: twoWeeks, }, - "notifications": map[string]interface{}{ - "start": twoWeeks.String(), + { + name: "without next_audit_date", + input: map[string]interface{}{ + "recurrence": map[string]interface{}{ + "frequency": "3 months", + "day_of_month": "1", + }, + "notifications": map[string]interface{}{ + "start": twoWeeks.String(), + }, + }, + expectedNextAudit: time.Time{}, + expectedRecurrence: Recurrence{ + Frequency: ThreeMonths, + DayOfMonth: FirstDayOfMonth, + }, + expectedNotificationStart: twoWeeks, }, } - data, err := json.Marshal(&raw) - require.NoError(t, err) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + data, err := json.Marshal(&tt.input) + require.NoError(t, err) - var audit Audit - require.NoError(t, json.Unmarshal(data, &audit)) + var audit Audit + require.NoError(t, json.Unmarshal(data, &audit)) - 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) - require.Equal(t, twoWeeks, audit.Notifications.Start) + require.Equal(t, tt.expectedNextAudit, audit.NextAuditDate) + require.Equal(t, tt.expectedRecurrence, audit.Recurrence) + require.Equal(t, tt.expectedNotificationStart, audit.Notifications.Start) + }) + } } func TestAccessListDefaults(t *testing.T) { diff --git a/docs/pages/access-controls/access-lists/reference.mdx b/docs/pages/access-controls/access-lists/reference.mdx index c379039b57eec..560505b10501e 100644 --- a/docs/pages/access-controls/access-lists/reference.mdx +++ b/docs/pages/access-controls/access-lists/reference.mdx @@ -72,11 +72,24 @@ spec: # audit defines how frequently an Access List and its membership must be audited, along # with the next audit date. audit: - # Audit frequency defaults to 6 months. Format to golang's time.ParseDuration function: - # https://pkg.go.dev/time#ParseDuration - frequency: 4380h + recurrence: + # Frequency is the frequency between access list reviews. + # Defaults to 6months. + # Possible values are: 1month, 3months, 6months, 1year + frequency: 6months + # DayOfMonth is the day of month subsequent reviews will be scheduled on. + # Defaults to 1. + # Possible values are: 1, 15, last + day_of_month: "1" # The next time this Access List must be audited by. - next_audit_date: "2024-01-01T00:00:00Z" + # If not set, the next audit date will be picked up automatically. + notifications: + # When the access-request plugins will start to notify before the audit + # deadline. Format to golang's time.ParseDuration function: + # https://pkg.go.dev/time#ParseDuration + # Defaults to two weeks. + start: 336h # two weeks + next_audit_date: "2025-01-01T00:00:00Z" description: "A description of the Access List and its purpose" # owners are a list of Teleport users who own the Access List. Provided the owners # meet the ownership requirements, these users can control membership requirements @@ -126,4 +139,4 @@ above) and run `tctl create `. Access Lists can be updated by using `t `tctl` also supports a subset of Access List focused commands under the `tctl acl` subcommand. Through these you can list Access Lists, get information about a particular Access Lists, and manage Access List users. To see more details, run `tctl acl --help`. More detail can be seen in the -[CLI Reference](../../reference/cli.mdx). \ No newline at end of file +[CLI Reference](../../reference/cli.mdx). diff --git a/tool/tctl/common/collection.go b/tool/tctl/common/collection.go index c53689e092033..92f44214c338a 100644 --- a/tool/tctl/common/collection.go +++ b/tool/tctl/common/collection.go @@ -30,6 +30,7 @@ import ( devicepb "github.com/gravitational/teleport/api/gen/proto/go/teleport/devicetrust/v1" loginrulepb "github.com/gravitational/teleport/api/gen/proto/go/teleport/loginrule/v1" "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/api/types/accesslist" "github.com/gravitational/teleport/api/types/discoveryconfig" "github.com/gravitational/teleport/api/types/externalauditstorage" "github.com/gravitational/teleport/api/types/secreports" @@ -1320,3 +1321,29 @@ func (c *serverInfoCollection) writeText(w io.Writer, verbose bool) error { _, err := t.AsBuffer().WriteTo(w) return trace.Wrap(err) } + +type accessListCollection struct { + accessLists []*accesslist.AccessList +} + +func (c *accessListCollection) resources() []types.Resource { + r := make([]types.Resource, len(c.accessLists)) + for i, resource := range c.accessLists { + r[i] = resource + } + return r +} + +func (c *accessListCollection) writeText(w io.Writer, verbose bool) error { + t := asciitable.MakeTable([]string{"Name", "Title", "Review Frequency", "Next Audit Date"}) + for _, al := range c.accessLists { + t.AddRow([]string{ + al.GetName(), + al.Spec.Title, + al.Spec.Audit.Recurrence.Frequency.String(), + al.Spec.Audit.NextAuditDate.Format(time.RFC822), + }) + } + _, err := t.AsBuffer().WriteTo(w) + return trace.Wrap(err) +} diff --git a/tool/tctl/common/resource_command.go b/tool/tctl/common/resource_command.go index 84a55dce1f1ff..5e3f15ba859a5 100644 --- a/tool/tctl/common/resource_command.go +++ b/tool/tctl/common/resource_command.go @@ -42,6 +42,7 @@ import ( loginrulepb "github.com/gravitational/teleport/api/gen/proto/go/teleport/loginrule/v1" "github.com/gravitational/teleport/api/internalutils/stream" "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/api/types/accesslist" "github.com/gravitational/teleport/api/types/discoveryconfig" "github.com/gravitational/teleport/api/types/externalauditstorage" "github.com/gravitational/teleport/api/types/installers" @@ -2270,6 +2271,16 @@ func (rc *ResourceCommand) getCollection(ctx context.Context, client auth.Client return nil, trace.Wrap(err) } return &serverInfoCollection{serverInfos: serverInfos}, nil + case types.KindAccessList: + if rc.ref.Name != "" { + resource, err := client.AccessListClient().GetAccessList(ctx, rc.ref.Name) + if err != nil { + return nil, trace.Wrap(err) + } + return &accessListCollection{accessLists: []*accesslist.AccessList{resource}}, nil + } + accessLists, err := client.AccessListClient().GetAccessLists(ctx) + return &accessListCollection{accessLists: accessLists}, trace.Wrap(err) } return nil, trace.BadParameter("getting %q is not supported", rc.ref.String()) }