Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds FriendlyName support for IC resources #50127

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion api/accessrequest/access_request.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func GetResourceDetails(ctx context.Context, clusterName string, lister client.L
// We're interested in hostname or friendly name details. These apply to
// nodes, app servers, user groups and Identity Center resources.
switch resourceID.Kind {
case types.KindNode, types.KindApp, types.KindUserGroup, types.KindIdentityCenterAccount:
case types.KindNode, types.KindApp, types.KindUserGroup, types.KindIdentityCenterAccount, types.KindIdentityCenterAccountAssignment:
resourceIDs = append(resourceIDs, resourceID)
}
}
Expand Down Expand Up @@ -89,6 +89,26 @@ func GetResourceDetails(ctx context.Context, clusterName string, lister client.L
Kind: resource.GetKind(),
Name: resource.GetName(),
}

// We pretend that AWS accounts are Apps for display, so we have to rewrite
// the `id` of the App resource returned by GetResourcesByResourceIDs()
// to that of the corresponding `IdentityCenterAccount` that the caller
// was asking for.
if resource.GetKind() == types.KindApp && resource.GetSubKind() == types.KindIdentityCenterAccount {
appResource, ok := resource.(*types.AppV3)
if !ok {
return nil, trace.BadParameter("invalid type for kind App: %T", resource)
}

icInfo := appResource.GetIdentityCenter()
if icInfo == nil {
return nil, trace.BadParameter("malformed Identity Center App: identity center info is missing")
}

id.Kind = types.KindIdentityCenterAccount
id.Name = icInfo.AccountID
}

result[types.ResourceIDToString(id)] = types.ResourceDetails{
FriendlyName: friendlyName,
}
Expand Down
14 changes: 14 additions & 0 deletions api/types/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"github.com/gravitational/trace"

"github.com/gravitational/teleport/api/defaults"
identitycenterv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/identitycenter/v1"
"github.com/gravitational/teleport/api/types/common"
"github.com/gravitational/teleport/api/types/compare"
"github.com/gravitational/teleport/api/utils"
Expand Down Expand Up @@ -705,6 +706,19 @@ func FriendlyName(resource ResourceWithLabels) string {
}

switch rr := resource.(type) {
case Resource153Unwrapper:
// RFD-153 style resources are generally data-only and do not have any
// methods beyond the minimal [Resource153] interface. Because we can't
// rely on them being able to implement an interface in order to generate
// a friendly name, any 153-style resources that *want* a friendly name
// will have to manually generate it.
switch urr := rr.Unwrap().(type) {
case *identitycenterv1.Account:
return urr.GetSpec().GetName()

case *identitycenterv1.AccountAssignment:
return urr.GetSpec().GetDisplay()
}
case interface{ GetHostname() string }:
return rr.GetHostname()
case interface{ GetDisplayName() string }:
Expand Down
46 changes: 46 additions & 0 deletions lib/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ import (
usagereporter "github.com/gravitational/teleport/lib/usagereporter/teleport"
"github.com/gravitational/teleport/lib/utils"
"github.com/gravitational/teleport/lib/utils/interval"
"github.com/gravitational/teleport/lib/utils/pagination"
vc "github.com/gravitational/teleport/lib/versioncontrol"
"github.com/gravitational/teleport/lib/versioncontrol/github"
uw "github.com/gravitational/teleport/lib/versioncontrol/upgradewindow"
Expand Down Expand Up @@ -6402,9 +6403,54 @@ func (a *Server) ListResources(ctx context.Context, req proto.ListResourcesReque
NextKey: wResp.NextKey,
}, nil
}
if req.ResourceType == types.KindIdentityCenterAccount {
return a.listIdentityCenterAccounts(ctx, req)
}
return a.Cache.ListResources(ctx, req)
}

func (a *Server) listIdentityCenterAccounts(ctx context.Context, req proto.ListResourcesRequest) (*types.ListResourcesResponse, error) {
filter := services.MatchResourceFilter{
ResourceKind: types.KindIdentityCenterAccount,
Labels: req.Labels,
SearchKeywords: req.SearchKeywords,
}

if req.PredicateExpression != "" {
expression, err := services.NewResourceExpression(req.PredicateExpression)
if err != nil {
return nil, trace.Wrap(err)
}
filter.PredicateExpression = expression
}

startKey := pagination.NewRequestToken(req.StartKey)

accounts, nextPage, err := a.Cache.ListIdentityCenterAccountsWithFilter(ctx, int(req.Limit), &startKey,
func(acct services.IdentityCenterAccount) bool {
match, err := services.MatchResourceByFilters(
types.Resource153ToResourceWithLabels(acct), filter, nil)
if err != nil {
a.logger.ErrorContext(ctx, "failed running matcher", "error", err)
return false
}
return match
})
if err != nil {
return nil, trace.Wrap(err)
}

resources := make([]types.ResourceWithLabels, len(accounts))
for i, acct := range accounts {
resources[i] = types.Resource153ToResourceWithLabels(acct)
}

return &types.ListResourcesResponse{
Resources: resources,
NextKey: string(nextPage),
}, nil
}

// CreateKubernetesCluster creates a new kubernetes cluster resource.
func (a *Server) CreateKubernetesCluster(ctx context.Context, kubeCluster types.KubeCluster) error {
if err := enforceLicense(types.KindKubernetesCluster); err != nil {
Expand Down
4 changes: 4 additions & 0 deletions lib/auth/authclient/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -1244,6 +1244,10 @@ type Cache interface {
// GetProvisioningState gets a specific provisioning state
GetProvisioningState(context.Context, services.DownstreamID, services.ProvisioningStateID) (*provisioningv1.PrincipalState, error)

// ListIdentityCenterAccountsWithFilter returns a paginated list of all
// Identity Center Accounts in the cache that match the supplied predicate
ListIdentityCenterAccountsWithFilter(context.Context, int, *pagination.PageRequestToken, func(services.IdentityCenterAccount) bool) ([]services.IdentityCenterAccount, pagination.NextPageToken, error)

// GetAccountAssignment fetches specific IdentityCenter Account Assignment
GetAccountAssignment(context.Context, services.IdentityCenterAccountAssignmentID) (services.IdentityCenterAccountAssignment, error)

Expand Down
20 changes: 20 additions & 0 deletions lib/cache/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -3620,6 +3620,26 @@ func (c *Cache) GetProvisioningState(ctx context.Context, downstream services.Do
return rg.reader.GetProvisioningState(ctx, downstream, id)
}

// ListIdentityCenterAccountsWithFilter returns a paginated list of all
// Identity Center Accounts in the cache that match the supplied predicate
func (c *Cache) ListIdentityCenterAccountsWithFilter(
ctx context.Context,
pageSize int,
pageToken *pagination.PageRequestToken,
filter func(services.IdentityCenterAccount) bool,
) ([]services.IdentityCenterAccount, pagination.NextPageToken, error) {
ctx, span := c.Tracer.Start(ctx, "cache/ListIdentityCenterAccountsWithFilter")
defer span.End()

rg, err := readCollectionCache(c, c.collections.identityCenterAccounts)
if err != nil {
return nil, "", trace.Wrap(err)
}
defer rg.Release()

return rg.reader.ListIdentityCenterAccountsWithFilter(ctx, pageSize, pageToken, filter)
}

func (c *Cache) GetAccountAssignment(ctx context.Context, id services.IdentityCenterAccountAssignmentID) (services.IdentityCenterAccountAssignment, error) {
ctx, span := c.Tracer.Start(ctx, "cache/GetAccountAssignment")
defer span.End()
Expand Down
1 change: 1 addition & 0 deletions lib/cache/identitycenter.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
type identityCenterAccountGetter interface {
GetIdentityCenterAccount(context.Context, services.IdentityCenterAccountID) (services.IdentityCenterAccount, error)
ListIdentityCenterAccounts(context.Context, int, *pagination.PageRequestToken) ([]services.IdentityCenterAccount, pagination.NextPageToken, error)
ListIdentityCenterAccountsWithFilter(context.Context, int, *pagination.PageRequestToken, func(services.IdentityCenterAccount) bool) ([]services.IdentityCenterAccount, pagination.NextPageToken, error)
}

type identityCenterAccountExecutor struct{}
Expand Down
8 changes: 8 additions & 0 deletions lib/services/identitycenter.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,14 @@ type IdentityCenterAccountGetter interface {
type IdentityCenterAccounts interface {
IdentityCenterAccountGetter

// ListIdentityCenterAccountsWithFilter lists Identity Center Accounts in the backend store
ListIdentityCenterAccountsWithFilter(
context.Context,
int,
*pagination.PageRequestToken,
func(IdentityCenterAccount) bool,
) ([]IdentityCenterAccount, pagination.NextPageToken, error)

// CreateIdentityCenterAccount creates a new Identity Center Account record
CreateIdentityCenterAccount(context.Context, IdentityCenterAccount) (IdentityCenterAccount, error)

Expand Down
34 changes: 34 additions & 0 deletions lib/services/local/identitycenter.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,40 @@ func (svc *IdentityCenterService) ListIdentityCenterAccounts(ctx context.Context
return result, pagination.NextPageToken(nextPage), nil
}

// ListIdentityCenterAccountsWithFilter returns a paginated list of all
// Identity Center Accounts in the service backend that match the supplied
// predicate
func (svc *IdentityCenterService) ListIdentityCenterAccountsWithFilter(
ctx context.Context,
pageSize int,
page *pagination.PageRequestToken,
matcher func(services.IdentityCenterAccount) bool,
) ([]services.IdentityCenterAccount, pagination.NextPageToken, error) {
if pageSize == 0 {
pageSize = identityCenterPageSize
}

pageToken, err := page.Consume()
if err != nil {
return nil, "", trace.Wrap(err, "listing identity center assignment records")
}

wrappedMatcher := func(a *identitycenterv1.Account) bool {
return matcher(services.IdentityCenterAccount{Account: a})
}

accounts, nextPage, err := svc.accounts.ListResourcesWithFilter(ctx, pageSize, pageToken, wrappedMatcher)
if err != nil {
return nil, "", trace.Wrap(err)
}

result := make([]services.IdentityCenterAccount, len(accounts))
for i, acct := range accounts {
result[i] = services.IdentityCenterAccount{Account: acct}
}
return result, pagination.NextPageToken(nextPage), nil
}

// CreateIdentityCenterAccount creates a new Identity Center Account record
func (svc *IdentityCenterService) CreateIdentityCenterAccount(ctx context.Context, acct services.IdentityCenterAccount) (services.IdentityCenterAccount, error) {
created, err := svc.accounts.CreateResource(ctx, acct.Account)
Expand Down
78 changes: 78 additions & 0 deletions lib/services/local/identitycenter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ package local
import (
"context"
"fmt"
"maps"
"slices"
"testing"

"github.com/gravitational/trace"
Expand All @@ -32,6 +34,7 @@ import (
"github.com/gravitational/teleport/lib/backend"
"github.com/gravitational/teleport/lib/backend/lite"
"github.com/gravitational/teleport/lib/services"
"github.com/gravitational/teleport/lib/utils/pagination"
)

func newTestBackend(t *testing.T, ctx context.Context, clock clockwork.Clock) backend.Backend {
Expand Down Expand Up @@ -270,6 +273,81 @@ func TestIdentityCenterResourceCRUD(t *testing.T) {
}
}

func TestIdentityCenterAccountListing(t *testing.T) {
// GIVEN a test cluster
ctx := newTestContext(t)
clock := clockwork.NewFakeClock()
backend := newTestBackend(t, ctx, clock)

// GIVEN an Identity Center Service
uut, err := NewIdentityCenterService(IdentityCenterServiceConfig{Backend: backend})
require.NoError(t, err)

// GIVEN a collection of Identity Center accounts
accounts := make(map[services.IdentityCenterAccountID]services.IdentityCenterAccount)
for _, id := range []string{"alpha", "bravo", "charlie", "delta", "echo", "foxtrot", "golf"} {
accounts[services.IdentityCenterAccountID(id)] =
makeTestIdentityCenterAccount(t, ctx, uut, id)
}

testCases := []struct {
name string
pageSize int
filter func(services.IdentityCenterAccount) bool
expected []services.IdentityCenterAccountID
}{
{
name: "full",
pageSize: 0,
filter: func(services.IdentityCenterAccount) bool { return true },
expected: slices.Collect(maps.Keys(accounts)),
},
{
name: "paged",
pageSize: 2,
filter: func(services.IdentityCenterAccount) bool { return true },
expected: slices.Collect(maps.Keys(accounts)),
},
{
name: "filtered",
pageSize: 3,
expected: []services.IdentityCenterAccountID{
"alpha", "charlie", "echo", "golf",
},
filter: func(acct services.IdentityCenterAccount) bool {
name := acct.Metadata.Name
return name == "alpha" || name == "charlie" ||
name == "echo" || name == "golf"
},
},
}

for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
var pageToken pagination.PageRequestToken
output := make(map[services.IdentityCenterAccountID]services.IdentityCenterAccount)
for {
page, nextPage, err := uut.ListIdentityCenterAccountsWithFilter(ctx, test.pageSize, &pageToken, test.filter)
require.NoError(t, err)

if test.pageSize != 0 {
require.LessOrEqual(t, len(page), test.pageSize)
}

for _, account := range page {
output[services.IdentityCenterAccountID(account.Metadata.Name)] = account
}

if nextPage == pagination.EndOfList {
break
}
pageToken.Update(nextPage)
}
require.Len(t, output, len(test.expected))
})
}
}

func makeTestIdentityCenterAccount(t *testing.T, ctx context.Context, svc services.IdentityCenter, id string) services.IdentityCenterAccount {
t.Helper()
created, err := svc.CreateIdentityCenterAccount(ctx, services.IdentityCenterAccount{
Expand Down
5 changes: 5 additions & 0 deletions lib/utils/pagination/pagination.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,11 @@ type PageRequestToken struct {
stale bool
}

// NewRequestToken wraps the supplied string in a PageRequestToken
func NewRequestToken(key string) PageRequestToken {
return PageRequestToken{token: key}
}

// Consume moves the token value out of the PageRequestToken and marks the token
// as stale. If the token is already stale, this method will fail with
// BadParameter.
Expand Down
Loading