Skip to content

Commit

Permalink
Add Identity Center Account Assignments to Unified Resource Cache (#4…
Browse files Browse the repository at this point in the history
…9580)

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 9, 2024
1 parent e2f3daf commit 10925bb
Show file tree
Hide file tree
Showing 12 changed files with 2,822 additions and 1,301 deletions.
4 changes: 4 additions & 0 deletions api/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -3810,6 +3810,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,459 changes: 2,326 additions & 1,133 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)
}
58 changes: 58 additions & 0 deletions api/proto/teleport/legacy/client/proto/authservice.proto
Original file line number Diff line number Diff line change
Expand Up @@ -1940,6 +1940,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 @@ -1970,6 +2024,10 @@ message PaginatedResource {
types.SAMLIdPServiceProviderV1 SAMLIdPServiceProvider = 12 [(gogoproto.jsontag) = "saml_idp_service_provider,omitempty"];
// GitServer represents a Git server resource.
types.ServerV2 git_server = 15;

// 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 @@ -1309,7 +1309,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 @@ -1693,7 +1694,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 @@ -1850,7 +1852,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 10925bb

Please sign in to comment.