Skip to content

Commit

Permalink
[v17] Add Identity Center Account Assignments to Unified Resource Cac…
Browse files Browse the repository at this point in the history
…he (#49976)

Backports #49580 and #49977

Adds Identity Center Account Assignments to the Unified resource cache
so they can be requested in access requests.

Unfortunately we can't just include an `identitycenterv1.AccountAssignment`
directly in the resource cache ListResources output because the legacy
protobuf codegen used for the authservice and the new codegen used for
identitycenter/v1 produce incompatible serialization code, so resulting
generated code does not compile.

To get around this issue, this change introduces a parallel (and slightly
simplified) definition of an IdentityCenterAccountAssignment in the authservice
protobuf spec to act as the wire format for this type. The cached
`identitycenterv1.AccountAssignment` resources are copied into a
`proto.IdentityCenterAccountAssignment` on a cache read.

Includes:
 - adding resources to cache
 - adding account assignment paginated resource
 - account assignment role condition matching for RBAC
  • Loading branch information
tcsc authored Dec 10, 2024
1 parent 3b225bf commit 7ff7a46
Show file tree
Hide file tree
Showing 12 changed files with 2,977 additions and 1,447 deletions.
4 changes: 4 additions & 0 deletions api/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -3808,6 +3808,10 @@ func (c *Client) ListResources(ctx context.Context, req proto.ListResourcesReque
resources[i] = respResource.GetSAMLIdPServiceProvider()
case types.KindIdentityCenterAccount:
resources[i] = respResource.GetAppServer()
case types.KindIdentityCenterAccountAssignment:
src := respResource.GetIdentityCenterAccountAssignment()
dst := proto.UnpackICAccountAssignment(src)
resources[i] = dst
default:
return nil, trace.NotImplemented("resource type %s does not support pagination", req.ResourceType)
}
Expand Down
3,754 changes: 2,473 additions & 1,281 deletions api/client/proto/authservice.pb.go

Large diffs are not rendered by default.

61 changes: 61 additions & 0 deletions api/client/proto/identitycenter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Copyright 2024 Gravitational, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package proto

import (
identitycenterv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/identitycenter/v1"
"github.com/gravitational/teleport/api/types"
)

// PackICAccountAssignment packs an Identity Center Account Assignment in to its
// wire format.
func PackICAccountAssignment(assignment *identitycenterv1.AccountAssignment) isPaginatedResource_Resource {
return &PaginatedResource_IdentityCenterAccountAssignment{
IdentityCenterAccountAssignment: &IdentityCenterAccountAssignment{
Kind: types.KindIdentityCenterAccountAssignment,
Version: assignment.GetVersion(),
Metadata: types.Metadata153ToLegacy(assignment.Metadata),
DisplayName: assignment.GetSpec().GetDisplay(),
Account: &IdentityCenterAccount{
AccountName: assignment.GetSpec().GetAccountName(),
ID: assignment.GetSpec().GetAccountId(),
},
PermissionSet: &IdentityCenterPermissionSet{
ARN: assignment.GetSpec().GetPermissionSet().GetArn(),
Name: assignment.GetSpec().GetPermissionSet().GetName(),
},
},
}
}

// UnpackICAccountAssignment converts a wire-format IdentityCenterAccountAssignment
// resource back into an identitycenterv1.AccountAssignment instance.
func UnpackICAccountAssignment(src *IdentityCenterAccountAssignment) types.ResourceWithLabels {
dst := &identitycenterv1.AccountAssignment{
Kind: types.KindIdentityCenterAccountAssignment,
Version: src.Version,
Metadata: types.LegacyTo153Metadata(src.Metadata),
Spec: &identitycenterv1.AccountAssignmentSpec{
AccountId: src.Account.ID,
AccountName: src.Account.AccountName,
Display: src.DisplayName,
PermissionSet: &identitycenterv1.PermissionSetInfo{
Arn: src.PermissionSet.ARN,
Name: src.PermissionSet.Name,
},
},
}
return types.Resource153ToResourceWithLabels(dst)
}
57 changes: 57 additions & 0 deletions api/proto/teleport/legacy/client/proto/authservice.proto
Original file line number Diff line number Diff line change
Expand Up @@ -1934,6 +1934,60 @@ message CreateRegisterChallengeRequest {
DeviceUsage DeviceUsage = 3 [(gogoproto.jsontag) = "device_usage,omitempty"];
}

// IdentityCenterAccount holds information about an Identity Center account
// within an IdentityCenterAccountAssignment
message IdentityCenterAccount {
// ID is the AWS-assigned account ID
string ID = 1;

// ARN is the full Amazon Resource Name for the AWS account
string ARN = 2;

// AccountName is the human-readable name of the account
string AccountName = 3;

// Description is a free text description of the account
string Description = 4;
}

// IdentityCenterPermissionSet holds information about an Identity Center
// permission set within an IdentityCenterAccountAssignment
message IdentityCenterPermissionSet {
// ARN is the full Amazon Resource Name for the Permission Set
string ARN = 1;

// Name is the human readable name for the Permission Set
string Name = 2;
}

// IdentityCenterAccountAssignment represents a requestable Identity Center
// Account Assignment. This is strictly a wire-format object for use with the
// Unfied resource cache, and the types defined in the `identitycenter` package
// should be used for actual processing.
message IdentityCenterAccountAssignment {
// Kind is the database server resource kind.
string Kind = 1 [(gogoproto.jsontag) = "kind"];
// SubKind is an optional resource subkind.
string SubKind = 2 [(gogoproto.jsontag) = "sub_kind,omitempty"];
// Version is the resource version.
string Version = 3 [(gogoproto.jsontag) = "version"];
// Metadata is the account metadata.
types.Metadata Metadata = 4 [
(gogoproto.nullable) = false,
(gogoproto.jsontag) = "metadata"
];

// DisplayName is a human-readable name for the Account assignment
string DisplayName = 5;

// Account is the Identity Center Account this assigment references
IdentityCenterAccount Account = 6;

// PermissionSet is the Identity Center Permission Set this assignment
// references
IdentityCenterPermissionSet PermissionSet = 7;
}

// PaginatedResource represents one of the supported resources.
message PaginatedResource {
// Resource is the resource itself.
Expand Down Expand Up @@ -1962,6 +2016,9 @@ message PaginatedResource {
types.AppServerOrSAMLIdPServiceProviderV1 AppServerOrSAMLIdPServiceProvider = 11 [deprecated = true];
// SAMLIdPServiceProvider represents a SAML IdP service provider resource.
types.SAMLIdPServiceProviderV1 SAMLIdPServiceProvider = 12 [(gogoproto.jsontag) = "saml_idp_service_provider,omitempty"];
// IdentityCenterAccountAssignment represents a requestable Identity Center
// Account Assignment
IdentityCenterAccountAssignment IdentityCenterAccountAssignment = 16 [(gogoproto.jsontag) = "identity_center_account_assignment,omitempty"];
}

// Logins allowed for the included resource. Only to be populated for SSH and Desktops.
Expand Down
108 changes: 66 additions & 42 deletions api/types/resource_153.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,9 @@ func (r *legacyToResource153Adapter) GetKind() string {
return r.inner.GetKind()
}

func (r *legacyToResource153Adapter) GetMetadata() *headerv1.Metadata {
md := r.inner.GetMetadata()

// LegacyTo153Metadata converts a legacy [Metadata] object an RFD153-style
// [headerv1.Metadata] block
func LegacyTo153Metadata(md Metadata) *headerv1.Metadata {
var expires *timestamppb.Timestamp
if md.Expires != nil {
expires = timestamppb.New(*md.Expires)
Expand All @@ -108,6 +108,10 @@ func (r *legacyToResource153Adapter) GetMetadata() *headerv1.Metadata {
}
}

func (r *legacyToResource153Adapter) GetMetadata() *headerv1.Metadata {
return LegacyTo153Metadata(r.inner.GetMetadata())
}

func (r *legacyToResource153Adapter) GetSubKind() string {
return r.inner.GetSubKind()
}
Expand Down Expand Up @@ -166,9 +170,9 @@ func (r *resource153ToLegacyAdapter) GetKind() string {
return r.inner.GetKind()
}

func (r *resource153ToLegacyAdapter) GetMetadata() Metadata {
md := r.inner.GetMetadata()

// Metadata153ToLegacy converts RFD153-style resource metadata to legacy
// metadata.
func Metadata153ToLegacy(md *headerv1.Metadata) Metadata {
// use zero time.time{} for zero *timestamppb.Timestamp, instead of 01/01/1970.
expires := md.Expires.AsTime()
if md.Expires == nil {
Expand All @@ -185,6 +189,10 @@ func (r *resource153ToLegacyAdapter) GetMetadata() Metadata {
}
}

func (r *resource153ToLegacyAdapter) GetMetadata() Metadata {
return Metadata153ToLegacy(r.inner.GetMetadata())
}

func (r *resource153ToLegacyAdapter) GetName() string {
return r.inner.GetMetadata().Name
}
Expand Down Expand Up @@ -217,41 +225,26 @@ func (r *resource153ToLegacyAdapter) SetSubKind(subKind string) {
panic("interface Resource153 does not implement SetSubKind")
}

// ClonableResource153 adds a restriction on [Resource153] such that implementors
// must have a CloneResource() method.
type ClonableResource153 interface {
Resource153
CloneResource() ClonableResource153
}

// UnifiedResource represents the combined set of interfaces that a resource
// must implement to be used with the Teleport Unified Resource Cache
type UnifiedResource interface {
ResourceWithLabels
CloneResource() ResourceWithLabels
}

// Resource153ToUnifiedResource wraps an RFD153-style resource in a type that
// implements the legacy [ResourceWithLabels] interface and is suitable for use
// with the Teleport Unified Resources Cache.
// Resource153ToResourceWithLabels wraps a [Resource153]-style resource in
// the legacy [Resource] and [ResourceWithLabels] interfaces.
//
// The same caveats that apply to [Resource153ToLegacy] apply.
func Resource153ToUnifiedResource(r ClonableResource153) UnifiedResource {
return &resource153ToUnifiedResourceAdapter{
resource153ToLegacyAdapter: resource153ToLegacyAdapter{
func Resource153ToResourceWithLabels(r Resource153) ResourceWithLabels {
return &resource153ToResourceWithLabelsAdapter{
resource153ToLegacyAdapter{
inner: r,
},
}
}

// resource153ToUnifiedResourceAdapter wraps a [resource153ToLegacyAdapter] to
// provide an implementation of [UnifiedResource]
type resource153ToUnifiedResourceAdapter struct {
// resource153ToResourceWithLabelsAdapter wraps a new-style resource in a
// type implementing the legacy resource interfaces
type resource153ToResourceWithLabelsAdapter struct {
resource153ToLegacyAdapter
}

// Origin implements ResourceWithLabels for the adapter.
func (r *resource153ToUnifiedResourceAdapter) Origin() string {
func (r *resource153ToResourceWithLabelsAdapter) Origin() string {
m := r.inner.GetMetadata()
if m == nil {
return ""
Expand All @@ -260,7 +253,7 @@ func (r *resource153ToUnifiedResourceAdapter) Origin() string {
}

// SetOrigin implements ResourceWithLabels for the adapter.
func (r *resource153ToUnifiedResourceAdapter) SetOrigin(origin string) {
func (r *resource153ToResourceWithLabelsAdapter) SetOrigin(origin string) {
m := r.inner.GetMetadata()
if m == nil {
return
Expand All @@ -269,7 +262,7 @@ func (r *resource153ToUnifiedResourceAdapter) SetOrigin(origin string) {
}

// GetLabel implements ResourceWithLabels for the adapter.
func (r *resource153ToUnifiedResourceAdapter) GetLabel(key string) (value string, ok bool) {
func (r *resource153ToResourceWithLabelsAdapter) GetLabel(key string) (value string, ok bool) {
m := r.inner.GetMetadata()
if m == nil {
return "", false
Expand All @@ -279,7 +272,7 @@ func (r *resource153ToUnifiedResourceAdapter) GetLabel(key string) (value string
}

// GetAllLabels implements ResourceWithLabels for the adapter.
func (r *resource153ToUnifiedResourceAdapter) GetAllLabels() map[string]string {
func (r *resource153ToResourceWithLabelsAdapter) GetAllLabels() map[string]string {
m := r.inner.GetMetadata()
if m == nil {
return nil
Expand All @@ -288,12 +281,12 @@ func (r *resource153ToUnifiedResourceAdapter) GetAllLabels() map[string]string {
}

// GetStaticLabels implements ResourceWithLabels for the adapter.
func (r *resource153ToUnifiedResourceAdapter) GetStaticLabels() map[string]string {
func (r *resource153ToResourceWithLabelsAdapter) GetStaticLabels() map[string]string {
return r.GetAllLabels()
}

// SetStaticLabels implements ResourceWithLabels for the adapter.
func (r *resource153ToUnifiedResourceAdapter) SetStaticLabels(labels map[string]string) {
func (r *resource153ToResourceWithLabelsAdapter) SetStaticLabels(labels map[string]string) {
m := r.inner.GetMetadata()
if m == nil {
return
Expand All @@ -304,23 +297,54 @@ func (r *resource153ToUnifiedResourceAdapter) SetStaticLabels(labels map[string]
// MatchSearch implements ResourceWithLabels for the adapter. If the underlying
// type exposes a MatchSearch method, this method will defer to that, otherwise
// it will match against the resource label values and name.
func (r *resource153ToUnifiedResourceAdapter) MatchSearch(searchValues []string) bool {
func (r *resource153ToResourceWithLabelsAdapter) MatchSearch(searchValues []string) bool {
if matcher, ok := r.inner.(interface{ MatchSearch([]string) bool }); ok {
return matcher.MatchSearch(searchValues)
}
fieldVals := append(utils.MapToStrings(r.GetAllLabels()), r.GetName())
return MatchSearch(fieldVals, searchValues, nil)
}

// ClonableResource153 adds a restriction on [Resource153] such that implementors
// must have a CloneResource() method.
type ClonableResource153 interface {
Resource153
CloneResource() ClonableResource153
}

// UnifiedResource represents the combined set of interfaces that a resource
// must implement to be used with the Teleport Unified Resource Cache
type UnifiedResource interface {
ResourceWithLabels
CloneResource() ResourceWithLabels
}

// Resource153ToUnifiedResource wraps an RFD153-style resource in a type that
// implements the legacy [ResourceWithLabels] interface and is suitable for use
// with the Teleport Unified Resources Cache.
//
// The same caveats that apply to [Resource153ToLegacy] apply.
func Resource153ToUnifiedResource(r ClonableResource153) UnifiedResource {
return &resource153ToUnifiedResourceAdapter{
resource153ToResourceWithLabelsAdapter: resource153ToResourceWithLabelsAdapter{
resource153ToLegacyAdapter{
inner: r,
},
},
}
}

// resource153ToUnifiedResourceAdapter wraps a [resource153ToLegacyAdapter] to
// provide an implementation of [UnifiedResource]
type resource153ToUnifiedResourceAdapter struct {
resource153ToResourceWithLabelsAdapter
}

// CloneResource clones the underlying resource and wraps it in
func (r *resource153ToUnifiedResourceAdapter) CloneResource() ResourceWithLabels {
// We assume that this type assertion will work because we force `inner`
// to implement ClonableResource153 in [Resource153ToUnifiedResource], which
// is the only externally-visible constructor function
// is the only externally-visible constructor function.
clone := r.inner.(ClonableResource153).CloneResource()
return &resource153ToUnifiedResourceAdapter{
resource153ToLegacyAdapter: resource153ToLegacyAdapter{
inner: clone,
},
}
return Resource153ToUnifiedResource(clone)
}
9 changes: 6 additions & 3 deletions lib/auth/auth_with_roles.go
Original file line number Diff line number Diff line change
Expand Up @@ -1338,7 +1338,8 @@ func (c *resourceAccess) checkAccess(resource types.ResourceWithLabels, filter s
type actionChecker func(namespace, resourceKind string, verbs ...string) error

func (a *ServerWithRoles) selectActionChecker(resourceKind string) actionChecker {
if resourceKind == types.KindIdentityCenterAccount {
switch resourceKind {
case types.KindIdentityCenterAccount, types.KindIdentityCenterAccountAssignment:
// Identity Center resources can be specified multiple ways in a Role
// Condition statement, so we need a special checker to handle it.
return a.identityCenterAction
Expand Down Expand Up @@ -1722,7 +1723,8 @@ func (a *ServerWithRoles) ListResources(ctx context.Context, req proto.ListResou
types.KindWindowsDesktopService,
types.KindUserGroup,
types.KindSAMLIdPServiceProvider,
types.KindIdentityCenterAccount:
types.KindIdentityCenterAccount,
types.KindIdentityCenterAccountAssignment:

default:
return nil, trace.NotImplemented("resource type %s does not support pagination", req.ResourceType)
Expand Down Expand Up @@ -1879,7 +1881,8 @@ func (a *ServerWithRoles) newResourceAccessChecker(resource string) (resourceAcc
types.KindUserGroup,
types.KindUnifiedResource,
types.KindSAMLIdPServiceProvider,
types.KindIdentityCenterAccount:
types.KindIdentityCenterAccount,
types.KindIdentityCenterAccountAssignment:
return &resourceChecker{AccessChecker: a.context.Checker}, nil
default:
return nil, trace.BadParameter("could not check access to resource type %s", resource)
Expand Down
Loading

0 comments on commit 7ff7a46

Please sign in to comment.