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

Exposes Identity Center accounts as Apps in Unified Resource Cache #49301

Merged
merged 5 commits into from
Dec 5, 2024

Conversation

tcsc
Copy link
Contributor

@tcsc tcsc commented Nov 21, 2024

For the purposes of the UI, Identity Center accounts and account
assignments are treated like special Apps. This patch exposes
Account Assignments to the UI via the Unified Resource Cache.

Includes:

  • Generating an App resource from an Identity Center Account resource
  • General plumbing from backend through to cache and UI

@tcsc tcsc added no-changelog Indicates that a PR does not require a changelog entry backport/branch/v17 labels Nov 21, 2024
@tcsc tcsc requested review from r0mant and smallinsky November 21, 2024 04:26
Copy link
Collaborator

@r0mant r0mant left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this would benefit from a review from someone on the scale team as well who is familiar with unified resources cache. @rosstimothy Who would be best to take a look?

@tcsc Also, I think we're getting overly excited about generics and iterators and should dial back. It is my strong opinion that they hurt readability a lot and we should be using them very sparingly and only if there's a really good reason. I think it took me 3x more time to review this than it should have because of trying to comprehend what all these templated types do and why are they needed and I strongly suspect we do not need any of them.

api/types/role.go Outdated Show resolved Hide resolved
api/utils/slices.go Outdated Show resolved Hide resolved
lib/auth/auth_with_roles.go Outdated Show resolved Hide resolved
lib/auth/auth_with_roles.go Outdated Show resolved Hide resolved
lib/services/unified_resource.go Outdated Show resolved Hide resolved
lib/services/unified_resource_adapter.go Outdated Show resolved Hide resolved
lib/web/ui/app.go Outdated Show resolved Hide resolved
@r0mant r0mant requested review from rosstimothy and removed request for ryanclark and Joerger November 21, 2024 22:51
@@ -470,6 +480,7 @@ func (c *UnifiedResourceCache) getResourcesAndUpdateCurrent(ctx context.Context)
putResources[types.KubeServer](c, newKubes)
putResources[types.SAMLIdPServiceProvider](c, newSAMLApps)
putResources[types.WindowsDesktop](c, newDesktops)
putResources[UnifiedResource153Adapter[IdentityCenterAccount]](c, newICAccounts)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How is this RFD 153 style resource going to work with the ListUnifiedResources RPC which requires a legacy gogo proto style resource? I thought when we discussed this in the past we decided that these should just be applications?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Accounts gets converted to an App on read

Copy link
Contributor

@rosstimothy rosstimothy Nov 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why can't they be inserted into the unified resource cache as an app? Wouldn't that reduce/eliminate 90% of the complexity introduced in this PR?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mainly so they are governed by the RBAC conditions for Identity Center resources, rather than app resources.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What can't RBAC be performed against the identity center resource if it detects the appropriate subkind?

lib/services/unified_resource_adapter.go Outdated Show resolved Hide resolved
lib/web/ui/app.go Outdated Show resolved Hide resolved
@tcsc tcsc force-pushed the tcsc/idc-expose-accounts-as-apps branch from 72ece0d to 9d1bdad Compare November 27, 2024 08:56
@tcsc tcsc requested a review from r0mant November 27, 2024 08:56
@tcsc tcsc force-pushed the tcsc/idc-expose-accounts-as-apps branch from eff54e2 to 34ea806 Compare November 28, 2024 03:05
Copy link
Contributor

@rosstimothy rosstimothy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest splitting this up into more focused PRs. There is a lot of what feels like glued on functionality that isn't directly related to unified resource caching being included here.

Comment on lines 38 to 40
// AssignmentName is the name of an account assignment representing this
// permission set being assigned to the enclosing account.
string assignment_name = 4;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move this out into it's own PR and leave this PR solely dedicated to unified resource caching

api/types/constants.go Outdated Show resolved Hide resolved
api/types/resource.go Outdated Show resolved Hide resolved
api/types/resource_153.go Outdated Show resolved Hide resolved
api/types/resource_153.go Outdated Show resolved Hide resolved
if cloner, ok := r.inner.(interface{ CloneResource() Resource153 }); ok {
return &Resource153ToLegacyAdapter{inner: cloner.CloneResource().(Resource153)}
}
panic("interface Resource153 does not implement CloneResource for the wrapped type")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we really panic here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what the correct alternative is for resources that don't implement CloneResource, short of reflect.DeepCopy.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An alternative approach would be to validate this during the LegacyToResource153() call when the adapter is created. This would allow you to return an error if the object does not fulfill the required interfaces for cloning.

We can create a new private adapter like toLegacyResourceWithLabels to avoid aligning already exiting code what uses LegacyToResource153

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is why I had a separate implementation in the first place.

Comment on lines 257 to 313
func (r *Resource153ToLegacyAdapter) MatchSearch(searchValues []string) bool {
fieldVals := append(utils.MapToStrings(r.GetAllLabels()), r.GetName())
return MatchSearch(fieldVals, searchValues, nil)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might work now, but I can imagine that it will be too limiting for a resource that wants to customize this behavior in the future.

api/types/resource_153.go Outdated Show resolved Hide resolved
lib/services/identitycenter.go Outdated Show resolved Hide resolved
lib/auth/auth_with_roles.go Outdated Show resolved Hide resolved
Comment on lines 965 to 966
URI: apiconstants.AWSConsoleURL,
PublicAddr: apiconstants.AWSConsoleURL,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The value of apiconstants.AWSConsoleURL is https://console.aws.amazon.com, which sends user to the root AWS console that lets them pick account with permission set. Since we want to launch users directly to AWS console with specific account+permission set, we need URL value that contains Identity Center instance ID and account ID.

In the POC, I used StartUrl field to contain such value https://github.com/gravitational/teleport.e/blob/sshah/idc-dev/lib/aws/identitycenter/accounts.go#L156. But in our current version, we have the StartUrl field defined but we missed to enrich the field with instance ID and account ID on initial account import.

tldr; We need to update the newIdentityCenterAccount func to enrich StartUrl field and the use the value here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the references here. Will update the account constructor in a later PR.

},
IdentityCenter: &types.AppIdentityCenter{
AccountID: acct.Spec.Id,
PermissionSets: pss,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given your another PR #49580 and that we use account assignment resource list to let user select desired permission set for access request, why do we need to include PermissionSets field and its value?

@tcsc tcsc force-pushed the tcsc/idc-expose-accounts-as-apps branch from e4d174c to 1ca7aec Compare December 4, 2024 11:46
@tcsc tcsc changed the base branch from master to tcsc/idc-add-assignments-to-apps December 4, 2024 11:47
@tcsc tcsc force-pushed the tcsc/idc-expose-accounts-as-apps branch 2 times, most recently from 8ff81be to 82f58ee Compare December 4, 2024 12:13
@tcsc
Copy link
Contributor Author

tcsc commented Dec 4, 2024

Label matching removed and replaces with custom role matchers.

Comment on lines 1352 to 1355
actionChecker := a.action
if kind == types.KindIdentityCenterAccount {
actionChecker = a.identityCenterAction
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you lave a comment explaining the custom behavior for the types.KindIdentityCenterAccount kind ?

Comment on lines +968 to +988
URI: acct.Spec.StartUrl,
PublicAddr: acct.Spec.StartUrl,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In current state the StartUrl is never set in our backend code:

e git:(tcsc/idc-ui) rg 'StartUrl'
web/teleport/src/services/upgradeWindow/upgradeWindow.ts
16:      .get(cfg.getWindowUpgradeStartUrl(clusterId))
31:      .post(cfg.getWindowUpgradeStartUrl(clusterId), req)

web/teleport/src/config.ts
182:  getWindowUpgradeStartUrl(clusterId: string) {

lib/aws/identitycenter/equal/equal_test.go
145:			StartUrl:            "https://example.com/start",
164:			StartUrl:            "https://example.com/start",

lib/aws/identitycenter/equal/account.go
21:		s1.GetStartUrl() == s2.GetStartUrl() &&

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is coming in a change discussed with @flyinghermit

// AssignmentName is the assignment resource that will provision an Account
// Assignment for this Permission Set on the enclosing account
AssignmentName string `json:"accountAssignment,omitempty"`
RequiresRequest bool `json:"requiresRequest,omitempty"`
Copy link
Contributor

@smallinsky smallinsky Dec 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now the RequiresRequest field is never set:

AssignmentName: srcPS.AssignmentName,

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will be used when we filter the resources based on access.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a go docs ?

lib/web/ui/app.go Outdated Show resolved Hide resolved
@@ -66,6 +69,9 @@ type App struct {
// Integration is the integration name that must be used to access this Application.
// Only applicable to AWS App Access.
Integration string `json:"integration,omitempty"`
// PermissionSets holds the permission sets that this app grants access to.
// Only valid for Identity Center Account apps
PermissionSets []IdentityCenterPermissionSet `json:"permission_sets,omitempty"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The json:camel_cases seems to be inconsistent with current json style for that uses PascalCase

https://github.com/gravitational/teleport/pull/49301/files#diff-f635fca1ca174ba271760ef6e8dbe4aa703df1ba9acde1ae8828b86fb8992631R68

return true, nil
}

psMatches, err := m.matchExpression(*(m.permissionSetARN), asmt.PermissionSet)
Copy link
Contributor

@smallinsky smallinsky Dec 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The naming conventions are quite vague - the asmt.PermissionSet is passed to matchExpression as accountExpression:
m.matchExpression(*(m.permissionSetARN), asmt.PermissionSet)

However, the naming of the second parameter in matchExpression suggests that it should represent an accountExpression:

func (m *IdentityCenterMatcher) matchExpression(target, accountExpression string) (bool, error) {

This inconsistency in parameter naming can be confusing and might lead to misunderstandings about the purpose of the method nad match logic.


// NewIdentityCenterAccountAssignmentMatcher creates a new [IdentityCenterMatcher]
// configured to match the supplied [IdentityCenterAccountAssignment].
func NewIdentityCenterAccountAssignmentMatcher(account IdentityCenterAccountAssignment) *IdentityCenterMatcher {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The AWS Account assignment in the business domain is defined as an accountID and permissionSetARN pair. How is it possible that an Account Assignment is missing the permissionSetARN element?

This creates confusion for the reader and demonstrates tight coupling in the current implementation. The matcher logic tries to couple two distinct objects.

I recommend splitting the matcher into separate matchers for each object. This would also help avoid logic like m.permissionSetARN == nil, which is unclear without a broader understanding of the context.

For example, it is not immediately obvious why the permissionSetARN can be empty when matching IdentityCenterAccountAssignments in current code

for _, asmt := range role.GetIdentityCenterAccountAssignments(condition) {
    accountMatches, err := m.matchExpression(m.accountID, asmt.Account)

    if !accountMatches {
        continue
    }
    // Why permissionSetARN is nil during  GetIdentityCenterAccountAssignment match check ? 
    if m.permissionSetARN == nil {
        return true, nil
    }

    psMatches, err := m.matchExpression(*(m.permissionSetARN), asmt.PermissionSet)
}

By decoupling the logic into separate matchers, the design would become clearer and easier to maintain and reason about business domain. It would also reduce the need for special-case-if logic.

Comment on lines 5901 to 5829
})

}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
})
}
})
}

api/types/role.go Show resolved Hide resolved
if cloner, ok := r.inner.(interface{ CloneResource() Resource153 }); ok {
return &Resource153ToLegacyAdapter{inner: cloner.CloneResource().(Resource153)}
}
panic("interface Resource153 does not implement CloneResource for the wrapped type")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An alternative approach would be to validate this during the LegacyToResource153() call when the adapter is created. This would allow you to return an error if the object does not fulfill the required interfaces for cloning.

We can create a new private adapter like toLegacyResourceWithLabels to avoid aligning already exiting code what uses LegacyToResource153

Copy link
Contributor

@smallinsky smallinsky left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have left some comment/suggestion

Also and wonder about:
#49301 (comment)

it feels like it’s attempting to introduce multiple changes at once—such as RBAC > checks, backend updates, and adding new resources.

To make the review process more efficient and focused it might be helpful to break > these changes into separate smaller PRs his way each part can be reviewed and > discussed independently.

For example - the PR description mentions: Exposes Identity Center accounts as Apps > in Unified Resource Cache but it’s not immediately clear how or why RBAC alignment > backend changes ** adding new resources** are tied to this goal.

Splitting the changes could make the purpose of each adjustment clearer and help reviewers better understand the overall intent.

I think that this is worth to consider as it could significantly improve the review process and the overall quality of the PR.

Copy link
Collaborator

@r0mant r0mant left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes much more sense than the previous iteration, lgtm once remaining feedback has been addressed.


// GetDisplayName fetches a human-readable display name for the App.
func (a *AppV3) GetDisplayName() string {
// Only Identity Center apps have a display name at this point. Returning
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically Okta apps also have a different display name because their actual name is an internal Okta ID. What do we use for them?

// umbrella `KindIdentityCenter` resource kind. This means that if access to the
// target resource is not explicitly denied, then the user has a second chance
// to get access via the generic resource kind.
func (a *ServerWithRoles) identityCenterActionNamespace(namespace string, resource string, verbs ...string) error {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: We literally never use a namespace other than "default", do we really need a separate identityCenterActionNamespace method?

I would consider just keeping identityCenterAction and hardcoding apidefaults.Namespace to keep things a little simpler.

// NewIdentityCenterAccountAssignmentMatcher creates a new [IdentityCenterMatcher]
// configured to match the supplied [IdentityCenterAccountAssignment].
func NewIdentityCenterAccountAssignmentMatcher(account IdentityCenterAccountAssignment) *IdentityCenterMatcher {
psARN := account.GetSpec().GetPermissionSet().GetArn()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can any of these be nil?

lib/services/identitycenter.go Outdated Show resolved Hide resolved
lib/services/identitycenter.go Outdated Show resolved Hide resolved
lib/services/role.go Outdated Show resolved Hide resolved
Comment on lines 2598 to 2608
if requiresLabelMatching {
matchLabels, labelsMessage, err := checkRoleLabelsMatch(types.Deny, role, traits, r, isDebugEnabled)
if err != nil {
return trace.Wrap(err)
}
if matchLabels {
debugf("Access to %v %q denied, deny rule in role %q matched; match(namespace=%v, %s)",
r.GetKind(), r.GetName(), role.GetName(), namespaceMessage, labelsMessage)
return trace.AccessDenied("access to %v denied. User does not have permissions. %v",
r.GetKind(), additionalDeniedMessage)
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has been here before though and I think "allow" and "deny" code paths are slightly different so may be tricky to factor out. I think it's ok to keep as-is.

@tcsc tcsc force-pushed the tcsc/idc-add-assignments-to-apps branch 2 times, most recently from 3cc204d to a3c8cdb Compare December 5, 2024 00:21
@tcsc tcsc force-pushed the tcsc/idc-expose-accounts-as-apps branch 2 times, most recently from 512945c to 8ecf2cd Compare December 5, 2024 04:58
@tcsc tcsc requested a review from smallinsky December 5, 2024 05:05
@tcsc
Copy link
Contributor Author

tcsc commented Dec 5, 2024

Moved RBAC in to another PR

@tcsc tcsc force-pushed the tcsc/idc-add-assignments-to-apps branch from f25e964 to 1e8f8bc Compare December 5, 2024 05:52
@tcsc tcsc force-pushed the tcsc/idc-expose-accounts-as-apps branch from 7bed34c to ba6f65d Compare December 5, 2024 06:32
Base automatically changed from tcsc/idc-add-assignments-to-apps to master December 5, 2024 06:38
    For the purposes of the UI, Identity Center accounts and account
    assignments are treated like special Apps. This patch exposes
    Account Assignments to the UI via the Unified Resource Cache.

    Includes:
    - Generating an App resource from an Identity Center Account resource
    - General plumbing from backend through to cache and UI
@tcsc tcsc force-pushed the tcsc/idc-expose-accounts-as-apps branch from ba6f65d to aa1132c Compare December 5, 2024 07:06
@tcsc tcsc enabled auto-merge December 5, 2024 10:54
@tcsc tcsc added this pull request to the merge queue Dec 5, 2024
Merged via the queue into master with commit e1be633 Dec 5, 2024
41 checks passed
@tcsc tcsc deleted the tcsc/idc-expose-accounts-as-apps branch December 5, 2024 12:43
@public-teleport-github-review-bot

@tcsc See the table below for backport results.

Branch Result
branch/v17 Failed

tcsc added a commit that referenced this pull request Dec 6, 2024
Backports #49301

For the purposes of the UI, Identity Center accounts and account
assignments are treated like special Apps. This patch exposes
Account Assignments to the UI via the Unified Resource Cache.

Includes:
 - Generating an App resource from an Identity Center Account resource
 - General plumbing from backend through to cache and UI
github-merge-queue bot pushed a commit that referenced this pull request Dec 6, 2024
…che (#49858)

Backports #49301

For the purposes of the UI, Identity Center accounts and account
assignments are treated like special Apps. This patch exposes
Account Assignments to the UI via the Unified Resource Cache.

Includes:
 - Generating an App resource from an Identity Center Account resource
 - General plumbing from backend through to cache and UI
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
aws-iam-identity-center backport/branch/v17 no-changelog Indicates that a PR does not require a changelog entry size/md
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants