Skip to content

Commit

Permalink
Notify CLI users when access lists need reviews. (#33468)
Browse files Browse the repository at this point in the history
When logging in via `tsh` or doing a `tsh status`, a message expressing which
access lists need to be reviewed will be displayed along with the amount of
time left until the next review.
  • Loading branch information
mdwn committed Oct 16, 2023
1 parent d85a4c3 commit caec073
Show file tree
Hide file tree
Showing 7 changed files with 631 additions and 390 deletions.
19 changes: 19 additions & 0 deletions api/client/accesslist/accesslist.go
Expand Up @@ -92,6 +92,25 @@ func (c *Client) GetAccessList(ctx context.Context, name string) (*accesslist.Ac
return accessList, trace.Wrap(err)
}

// GetAccessListsToReview returns access lists that the user needs to review.
func (c *Client) GetAccessListsToReview(ctx context.Context) ([]*accesslist.AccessList, error) {
resp, err := c.grpcClient.GetAccessListsToReview(ctx, &accesslistv1.GetAccessListsToReviewRequest{})
if err != nil {
return nil, trace.Wrap(err)
}

accessLists := make([]*accesslist.AccessList, len(resp.AccessLists))
for i, accessList := range resp.AccessLists {
var err error
accessLists[i], err = conv.FromProto(accessList)
if err != nil {
return nil, trace.Wrap(err)
}
}

return accessLists, nil
}

// UpsertAccessList creates or updates an access list resource.
func (c *Client) UpsertAccessList(ctx context.Context, accessList *accesslist.AccessList) (*accesslist.AccessList, error) {
resp, err := c.grpcClient.UpsertAccessList(ctx, &accesslistv1.UpsertAccessListRequest{
Expand Down
883 changes: 508 additions & 375 deletions api/gen/proto/go/teleport/accesslist/v1/accesslist_service.pb.go

Large diffs are not rendered by default.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions api/proto/teleport/accesslist/v1/accesslist_service.proto
Expand Up @@ -37,6 +37,8 @@ service AccessListService {
rpc DeleteAccessList(DeleteAccessListRequest) returns (google.protobuf.Empty);
// DeleteAllAccessLists hard deletes all access lists.
rpc DeleteAllAccessLists(DeleteAllAccessListsRequest) returns (google.protobuf.Empty);
// GetAccessListsToReview will return access lists that need to be reviewed by the current user.
rpc GetAccessListsToReview(GetAccessListsToReviewRequest) returns (GetAccessListsToReviewResponse);

// ListAccessListMembers returns a paginated list of all access list members.
rpc ListAccessListMembers(ListAccessListMembersRequest) returns (ListAccessListMembersResponse);
Expand Down Expand Up @@ -113,6 +115,14 @@ message DeleteAccessListRequest {
// DeleteAllAccessListsRequest is the request for deleting all access lists.
message DeleteAllAccessListsRequest {}

// GetAccessListsToReviewRequest is the request for getting access lists that the current user needs to review.
message GetAccessListsToReviewRequest {}

// GetAccessListsToReviewResponse is the response for getting access lists that the current user needs to review.
message GetAccessListsToReviewResponse {
repeated AccessList access_lists = 1;
}

// ListAccessListMembersRequest is the request for getting paginated access list members.
message ListAccessListMembersRequest {
// page_size is the size of the page to request.
Expand Down
2 changes: 2 additions & 0 deletions lib/services/access_list.go
Expand Up @@ -42,6 +42,8 @@ type AccessListsGetter interface {
ListAccessLists(context.Context, int, string) ([]*accesslist.AccessList, string, error)
// GetAccessList returns the specified access list resource.
GetAccessList(context.Context, string) (*accesslist.AccessList, error)
// GetAccessListsToReview returns access lists that the user needs to review.
GetAccessListsToReview(context.Context) ([]*accesslist.AccessList, error)
}

// AccessLists defines an interface for managing AccessLists.
Expand Down
5 changes: 5 additions & 0 deletions lib/services/local/access_list.go
Expand Up @@ -129,6 +129,11 @@ func (a *AccessListService) GetAccessList(ctx context.Context, name string) (*ac
return accessList, trace.Wrap(err)
}

// GetAccessListsToReview returns access lists that the user needs to review. This is not implemented in the local service.
func (a *AccessListService) GetAccessListsToReview(ctx context.Context) ([]*accesslist.AccessList, error) {
return nil, trace.NotImplemented("GetAccessListsToReview should not be called")
}

// UpsertAccessList creates or updates an access list resource.
func (a *AccessListService) UpsertAccessList(ctx context.Context, accessList *accesslist.AccessList) (*accesslist.AccessList, error) {
err := a.service.RunWhileLocked(ctx, lockName(accessList.GetName()), accessListLockTTL, func(ctx context.Context, _ backend.Backend) error {
Expand Down
63 changes: 48 additions & 15 deletions tool/tsh/common/tsh.go
Expand Up @@ -58,6 +58,7 @@ import (
apidefaults "github.com/gravitational/teleport/api/defaults"
"github.com/gravitational/teleport/api/profile"
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/api/types/accesslist"
apievents "github.com/gravitational/teleport/api/types/events"
"github.com/gravitational/teleport/api/types/wrappers"
"github.com/gravitational/teleport/api/utils/keys"
Expand Down Expand Up @@ -1798,7 +1799,7 @@ func onLogin(cf *CLIConf) error {
return trace.Wrap(err)
}

return trace.Wrap(printProfiles(cf, profile, profiles))
return trace.Wrap(printLoginInformation(cf, profile, profiles, cf.getAccessListsToReview(tc)))

// if the proxy names match but nothing else is specified; show motd and update active profile and kube configs
case host(cf.Proxy) == host(profile.ProxyURL.Host) &&
Expand All @@ -1821,7 +1822,7 @@ func onLogin(cf *CLIConf) error {
}

// Print status to show information of the logged in user.
return trace.Wrap(printProfiles(cf, profile, profiles))
return trace.Wrap(printLoginInformation(cf, profile, profiles, cf.getAccessListsToReview(tc)))
}

// proxy is unspecified or the same as the currently provided proxy,
Expand All @@ -1848,7 +1849,7 @@ func onLogin(cf *CLIConf) error {
}

// Print status to show information of the logged in user.
return trace.Wrap(printProfiles(cf, profile, profiles))
return trace.Wrap(printLoginInformation(cf, profile, profiles, cf.getAccessListsToReview(tc)))
// proxy is unspecified or the same as the currently provided proxy,
// but desired roles or request ID is specified, treat this as a
// privilege escalation request for the same login session.
Expand All @@ -1864,7 +1865,7 @@ func onLogin(cf *CLIConf) error {
return trace.Wrap(err)
}
// Print status to show information of the logged in user.
return trace.Wrap(printProfiles(cf, profile, profiles))
return trace.Wrap(printLoginInformation(cf, profile, profiles, cf.getAccessListsToReview(tc)))

// otherwise just pass through to standard login
default:
Expand Down Expand Up @@ -2011,7 +2012,7 @@ func onLogin(cf *CLIConf) error {
}

// Print status to show information of the logged in user.
if err := printProfiles(cf, profile, profiles); err != nil {
if err := printLoginInformation(cf, profile, profiles, cf.getAccessListsToReview(tc)); err != nil {
return trace.Wrap(err)
}

Expand Down Expand Up @@ -4089,9 +4090,8 @@ func printStatus(debug bool, p *profileInfo, env map[string]string, isActive boo
fmt.Printf("\n")
}

// printProfiles displays the provided profile information
// to the user.
func printProfiles(cf *CLIConf, profile *client.ProfileStatus, profiles []*client.ProfileStatus) error {
// printLoginInformation displays the provided profile information to the user.
func printLoginInformation(cf *CLIConf, profile *client.ProfileStatus, profiles []*client.ProfileStatus, accessListsToReview []*accesslist.AccessList) error {
env := getTshEnv()
active, others := makeAllProfileInfo(profile, profiles, env)

Expand Down Expand Up @@ -4129,6 +4129,16 @@ func printProfiles(cf *CLIConf, profile *client.ProfileStatus, profiles []*clien
}
}

if len(accessListsToReview) > 0 {
fmt.Printf("Access lists that need to be reviewed:\n")
now := time.Now()

for _, accessList := range accessListsToReview {
fmt.Printf("\t%s (%s left to review)\n", accessList.GetName(), accessList.Spec.Audit.NextAuditDate.Sub(now).Round(time.Second).String())
}
fmt.Println()
}

return nil
}

Expand All @@ -4147,7 +4157,14 @@ func onStatus(cf *CLIConf) error {
return trace.Wrap(err)
}

if err := printProfiles(cf, profile, profiles); err != nil {
// make the teleport client and retrieve the certificate from the proxy:
tc, err := makeClient(cf)
if err != nil {
log.WithError(err).Warn("Failed to make client for retrieving cluster alerts.")
return trace.Wrap(err)
}

if err := printLoginInformation(cf, profile, profiles, cf.getAccessListsToReview(tc)); err != nil {
return trace.Wrap(err)
}

Expand All @@ -4160,12 +4177,6 @@ func onStatus(cf *CLIConf) error {
return trace.NotFound("Active profile expired.")
}

tc, err := makeClient(cf)
if err != nil {
log.WithError(err).Warn("Failed to make client for retrieving cluster alerts.")
return nil
}

if tc.PrivateKeyPolicy.MFAVerified() {
log.Debug("Skipping cluster alerts due to Hardware Key PIN/Touch requirement.")
} else {
Expand Down Expand Up @@ -4828,6 +4839,28 @@ func onHeadlessApprove(cf *CLIConf) error {
return trace.Wrap(err)
}

// getAccessListsToReview will return access lists that the logged in user needs to review. On error,
// this will return an empty list.
func (cf *CLIConf) getAccessListsToReview(tc *client.TeleportClient) []*accesslist.AccessList {
clusterClient, err := tc.ConnectToCluster(cf.Context)
if err != nil {
log.WithError(err).Debug("Error connecting to the cluster")
return nil
}
defer func() {
clusterClient.Close()
}()

// Get the access lists to review. If the call returns NotImplemented, ignore it, as we may be communicating with an OSS
// server, which does not support access lists.
accessListsToReview, err := clusterClient.AuthClient.AccessListClient().GetAccessListsToReview(cf.Context)
if err != nil && !trace.IsNotImplemented(err) {
log.WithError(err).Debug("Error getting access lists to review")
}

return accessListsToReview
}

var mlockModes = []string{mlockModeNo, mlockModeAuto, mlockModeBestEffort, mlockModeStrict}

const (
Expand Down

0 comments on commit caec073

Please sign in to comment.