Skip to content

Commit

Permalink
Adds CRUD service for Identity Center resources
Browse files Browse the repository at this point in the history
This is just the pure CRUD read/write. Caching & watching support coming as
necessary in subsequent PRs.

Co-authored-by: Pawel Kopiczko <[email protected]>
Co-authored-by: Sakshyam Shah <[email protected]>
  • Loading branch information
3 people committed Oct 17, 2024
1 parent 77f531d commit c547c76
Show file tree
Hide file tree
Showing 10 changed files with 1,046 additions and 3 deletions.
14 changes: 14 additions & 0 deletions api/types/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,20 @@ const (
// KindStaticHostUser is a host user to be created on matching SSH nodes.
KindStaticHostUser = "static_host_user"

// KindIdentityCenterAccount describes an Identity-Center managed AWS Account
KindIdentityCenterAccount = "aws_ic_account"

// KindIdentityCenterPermissionSet describes an AWS Identity Center Permission Set
KindIdentityCenterPermissionSet = "aws_ic_permission_set"

// KindIdentityCenterPermissionSet describes an AWS Principal Assignment, representing
// a collection Account Assignments assigned to a Teleport User or AccessList
KindIdentityCenterPrincipalAssignment = "aws_ic_principal_assignment"

// KindIdentityCenterAccountAssignment describes an AWS Account and Permission Set
// pair that can be requested by a Teleport User.
KindIdentityCenterAccountAssignment = "aws_ic_account_assignment"

// MetaNameAccessGraphSettings is the exact name of the singleton resource holding
// access graph settings.
MetaNameAccessGraphSettings = "access-graph-settings"
Expand Down
1 change: 1 addition & 0 deletions lib/auth/accesspoint/accesspoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ type Config struct {
WindowsDesktops services.WindowsDesktops
AutoUpdateService services.AutoUpdateServiceGetter
ProvisioningStates services.ProvisioningStates
IdentityCenter services.IdentityCenter
}

func (c *Config) CheckAndSetDefaults() error {
Expand Down
10 changes: 9 additions & 1 deletion lib/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,14 @@ func NewServer(cfg *InitConfig, opts ...ServerOption) (*Server, error) {
if cfg.ProvisioningStates == nil {
cfg.ProvisioningStates, err = local.NewProvisioningStateService(cfg.Backend)
if err != nil {
return nil, trace.Wrap(err, "Creating provisioning state service")
return nil, trace.Wrap(err)
}
}
if cfg.IdentityCenter == nil {
svcCfg := local.IdentityCenterServiceConfig{Backend: cfg.Backend}
cfg.IdentityCenter, err = local.NewIdentityCenterService(svcCfg)
if err != nil {
return nil, trace.Wrap(err)
}
}
if cfg.CloudClients == nil {
Expand Down Expand Up @@ -676,6 +683,7 @@ type Services struct {
services.StaticHostUser
services.AutoUpdateService
services.ProvisioningStates
services.IdentityCenter
}

// GetWebSession returns existing web session described by req.
Expand Down
1 change: 1 addition & 0 deletions lib/auth/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,7 @@ func NewTestAuthServer(cfg TestAuthServerConfig) (*TestAuthServer, error) {
DiscoveryConfigs: svces.DiscoveryConfigs,
DynamicAccess: svces.DynamicAccessExt,
Events: svces.Events,
IdentityCenter: svces.IdentityCenter,
Integrations: svces.Integrations,
KubeWaitingContainers: svces.KubeWaitingContainer,
Kubernetes: svces.Kubernetes,
Expand Down
4 changes: 4 additions & 0 deletions lib/auth/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,10 @@ type InitConfig struct {

// Logger is the logger instance for the auth service to use.
Logger *slog.Logger

// IdentityCenter is the Identity Center state storage service to use in
// this node.
IdentityCenter services.IdentityCenter
}

// Init instantiates and configures an instance of AuthServer
Expand Down
220 changes: 220 additions & 0 deletions lib/services/identitycenter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
// Teleport
// Copyright (C) 2024 Gravitational, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

package services

import (
"context"

"google.golang.org/protobuf/proto"

identitycenterv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/identitycenter/v1"
"github.com/gravitational/teleport/lib/utils/pagination"
)

// IdentityCenterAccount wraps a raw identity center record in a new type to
// allow it to implement the interfaces required for use with the Unified
// Resource listing.
//
// IdentityCenterAccount simply wraps a pointer to the underlying
// identitycenterv1.Account record, and can be treated as a reference-like type.
// Copies of an IdentityCenterAccount will point to the same record.
type IdentityCenterAccount struct {
// This wrapper needs to:
// - implement the interfaces required for use with the Unified Resource
// service.
// - expose the existing interfaces & methods on the underlying
// identitycenterv1.Account
// - avoid copying the underlying identitycenterv1.Account due to embedded
// mutexes in the protobuf-generated code
//
// Given those requirements, storing an embedded pointer seems to be the
// least-bad approach.

*identitycenterv1.Account
}

// CloneResource creates a deep copy of the underlying account resource
func (a *IdentityCenterAccount) CloneResource() IdentityCenterAccount {
return IdentityCenterAccount{
Account: proto.Clone(a.Account).(*identitycenterv1.Account),
}
}

// IdentityCenterAccountID is a strongly-typed Identity Center account ID.
type IdentityCenterAccountID string

// IdentityCenterAccountGetter provides read-only access to Identity Center
// Account records
type IdentityCenterAccountGetter interface {
// ListIdentityCenterAccounts provides a paged list of all known identity
// center accounts
ListIdentityCenterAccounts(context.Context, int, *pagination.PageRequestToken) ([]IdentityCenterAccount, pagination.NextPageToken, error)

// GetIdentityCenterAccount fetches a specific Identity Center Account
GetIdentityCenterAccount(context.Context, IdentityCenterAccountID) (IdentityCenterAccount, error)
}

// IdentityCenterAccounts defines read/write access to Identity Center account
// resources
type IdentityCenterAccounts interface {
IdentityCenterAccountGetter

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

// UpdateIdentityCenterAccount performs a conditional update on an Identity
// Center Account record, returning the updated record on success.
UpdateIdentityCenterAccount(context.Context, IdentityCenterAccount) (IdentityCenterAccount, error)

// UpsertIdentityCenterAccount performs an *unconditional* upsert on an
// Identity Center Account record, returning the updated record on success.
// Be careful when mixing UpsertIdentityCenterAccount() with resources
// protected by optimistic locking
UpsertIdentityCenterAccount(context.Context, IdentityCenterAccount) (IdentityCenterAccount, error)

// DeleteIdentityCenterAccount deletes an Identity Center Account record
DeleteIdentityCenterAccount(context.Context, IdentityCenterAccountID) error

// DeleteAllIdentityCenterAccounts deletes all Identity Center Account records
DeleteAllIdentityCenterAccounts(context.Context) error
}

// PrincipalAssignmentID is a strongly-typed ID for Identity Center Principal
// Assignments
type PrincipalAssignmentID string

// IdentityCenterPrincipalAssignments defines operations on an Identity Center
// principal assignment database
type IdentityCenterPrincipalAssignments interface {
// ListPrincipalAssignments lists all PrincipalAssignment records in the
// service
ListPrincipalAssignments(context.Context, int, *pagination.PageRequestToken) ([]*identitycenterv1.PrincipalAssignment, pagination.NextPageToken, error)

// CreatePrincipalAssignment creates a new Principal Assignment record in
// the service from the supplied in-memory representation. Returns the
// created record on success.
CreatePrincipalAssignment(context.Context, *identitycenterv1.PrincipalAssignment) (*identitycenterv1.PrincipalAssignment, error)

// GetPrincipalAssignment fetches a specific Principal Assignment record.
GetPrincipalAssignment(context.Context, PrincipalAssignmentID) (*identitycenterv1.PrincipalAssignment, error)

// UpdatePrincipalAssignment performs a conditional update on a Principal
// Assignment record
UpdatePrincipalAssignment(context.Context, *identitycenterv1.PrincipalAssignment) (*identitycenterv1.PrincipalAssignment, error)

// UpsertPrincipalAssignment performs an unconditional update on a Principal
// Assignment record
UpsertPrincipalAssignment(context.Context, *identitycenterv1.PrincipalAssignment) (*identitycenterv1.PrincipalAssignment, error)

// DeletePrincipalAssignment deletes a specific principal assignment record
DeletePrincipalAssignment(context.Context, PrincipalAssignmentID) error

// DeleteAllPrincipalAssignments deletes all assignment record
DeleteAllPrincipalAssignments(context.Context) error
}

// PermissionSetID is a strongly typed ID for an identitycenterv1.PermissionSet
type PermissionSetID string

// IdentityCenterPermissionSets defines the operations to create and maintain
// identitycenterv1.PermissionSet records in the service.
type IdentityCenterPermissionSets interface {
// ListPermissionSets list the known Permission Sets
ListPermissionSets(context.Context, int, *pagination.PageRequestToken) ([]*identitycenterv1.PermissionSet, pagination.NextPageToken, error)

// CreatePermissionSet creates a new PermissionSet record based on the
// supplied in-memory representation, returning the created record on
// success
CreatePermissionSet(context.Context, *identitycenterv1.PermissionSet) (*identitycenterv1.PermissionSet, error)

// GetPermissionSet fetches a specific PermissionSet record
GetPermissionSet(context.Context, PermissionSetID) (*identitycenterv1.PermissionSet, error)

// UpdatePermissionSet performs a conditional update on the supplied Identity
// Center Permission Set
UpdatePermissionSet(context.Context, *identitycenterv1.PermissionSet) (*identitycenterv1.PermissionSet, error)

// DeletePermissionSet deletes a specific Identity Center PermissionSet
DeletePermissionSet(context.Context, PermissionSetID) error
}

// IdentityCenterAccountAssignment wraps a raw identitycenterv1.AccountAssignment
// record in a new type to allow it to implement the interfaces required for use
// with the Unified Resource listing. IdentityCenterAccountAssignment simply
// wraps a pointer to the underlying account record, and can be treated as a
// reference-like type.
//
// Copies of an IdentityCenterAccountAssignment will point to the same record.
type IdentityCenterAccountAssignment struct {
// This wrapper needs to:
// - implement the interfaces required for use with the Unified Resource
// service.
// - expose the existing interfaces & methods on the underlying
// identitycenterv1.AccountAssignment
// - avoid copying the underlying identitycenterv1.AccountAssignment due to
// embedded mutexes in the protobuf-generated code
//
// Given those requirements, storing an embedded pointer seems to be the
// least-bad approach.

*identitycenterv1.AccountAssignment
}

// CloneResource creates a deep copy of the underlying account resource
func (a IdentityCenterAccountAssignment) CloneResource() IdentityCenterAccountAssignment {
return IdentityCenterAccountAssignment{
AccountAssignment: proto.Clone(a.AccountAssignment).(*identitycenterv1.AccountAssignment),
}
}

// IdentityCenterAccountAssignmentID is a strongly typed ID for an
// IdentityCenterAccountAssignment
type IdentityCenterAccountAssignmentID string

// IdentityCenterAccountAssignments defines the operations to create and maintain
// Identity Center account assignment records in the service.
type IdentityCenterAccountAssignments interface {
// ListAccountAssignments lists all IdentityCenterAccountAssignment record
// known to the service
ListAccountAssignments(context.Context, int, *pagination.PageRequestToken) ([]IdentityCenterAccountAssignment, pagination.NextPageToken, error)

// CreateAccountAssignment creates a new Account Assignment record in
// the service from the supplied in-memory representation. Returns the
// created record on success.
CreateAccountAssignment(context.Context, IdentityCenterAccountAssignment) (IdentityCenterAccountAssignment, error)

// GetAccountAssignment fetches a specific Account Assignment record.
GetAccountAssignment(context.Context, IdentityCenterAccountAssignmentID) (IdentityCenterAccountAssignment, error)

// UpdateAccountAssignment performs a conditional update on the supplied
// Account Assignment, returning the updated record on success.
UpdateAccountAssignment(context.Context, IdentityCenterAccountAssignment) (IdentityCenterAccountAssignment, error)

// DeleteAccountAssignment deletes a specific account assignment
DeleteAccountAssignment(context.Context, IdentityCenterAccountAssignmentID) error

// DeleteAllAccountAssignments deletes all known account assignments
DeleteAllAccountAssignments(context.Context) error
}

// IdentityCenter combines all the resource managers used by the Identity Center plugin
type IdentityCenter interface {
IdentityCenterAccounts
IdentityCenterPermissionSets
IdentityCenterPrincipalAssignments
IdentityCenterAccountAssignments
}
97 changes: 97 additions & 0 deletions lib/services/identitycenter_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// Teleport
// Copyright (C) 2024 Gravitational, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

package services

import (
"testing"

"github.com/stretchr/testify/require"

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

func TestIdentityCenterAccountClone(t *testing.T) {
// GIVEN an Account Record
src := IdentityCenterAccount{
Account: &identitycenterv1.Account{
Kind: types.KindIdentityCenterAccount,
Version: types.V1,
Metadata: &headerv1.Metadata{Name: "some-account"},
Spec: &identitycenterv1.AccountSpec{
Id: "aws-account-id",
Arn: "arn:aws:sso::account-id:",
Description: "Test account",
PermissionSetInfo: []*identitycenterv1.PermissionSetInfo{
{
Name: "original value",
Arn: "arn:aws:sso:::permissionSet/ic-instance/ps-instance",
},
},
},
},
}

// WHEN I clone the resource
dst := src.CloneResource()

// EXPECT that the resulting clone compares equally
require.Equal(t, src, dst)

// WHEN I modify the source object in a way that would be shared with a
// shallow copy
src.Spec.PermissionSetInfo[0].Name = "some new value"

// EXPECT that the cloned object DOES NOT inherit the update
require.NotEqual(t, src, dst)
require.Equal(t, "original value", dst.Spec.PermissionSetInfo[0].Name)
}

func TestIdentityCenterAccountAssignmentClone(t *testing.T) {
// GIVEN an Account Assignment Record
src := IdentityCenterAccountAssignment{
AccountAssignment: &identitycenterv1.AccountAssignment{
Kind: types.KindIdentityCenterAccountAssignment,
Version: types.V1,
Metadata: &headerv1.Metadata{Name: "[email protected]"},
Spec: &identitycenterv1.AccountAssignmentSpec{
Display: "Some-Permission-set on Some-AWS-account",
PermissionSet: &identitycenterv1.PermissionSetInfo{
Arn: "arn:aws:sso:::permissionSet/ic-instance/ps-instance",
Name: "original name",
},
AccountName: "Some Account Name",
AccountId: "some account id",
},
},
}

// WHEN I clone the resource
dst := src.CloneResource()

// EXPECT that the resulting clone compares equally
require.Equal(t, src, dst)

// WHEN I modify the source object in a way that would be shared with a
// shallow copy
src.Spec.PermissionSet.Name = "some new name"

// EXPECT that the cloned object DOES NOT inherit the update
require.NotEqual(t, src, dst)
require.Equal(t, "original name", dst.Spec.PermissionSet.Name)
}
Loading

0 comments on commit c547c76

Please sign in to comment.