mirror of
https://github.com/gravitational/teleport.git
synced 2026-11-02 23:31:08 +08:00
Adds CRUD service for Identity Center resources (#47609)
* Adds CRUD service for Identity Center resources This is just the pure CRUD read/write. Caching & watching support coming as necessary in subsequent PRs. Co-authored-by: Pawel Kopiczko <kopiczko@gmail.com> Co-authored-by: Sakshyam Shah <sshah@goteleport.com> * Update identitycenter.go * linter appeasement --------- Co-authored-by: Pawel Kopiczko <kopiczko@gmail.com> Co-authored-by: Sakshyam Shah <sshah@goteleport.com>
This commit is contained in:
co-authored by
Pawel Kopiczko
Sakshyam Shah
parent
28a68483e0
commit
bc06d0f55c
@@ -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"
|
||||
|
||||
@@ -106,6 +106,7 @@ type Config struct {
|
||||
WindowsDesktops services.WindowsDesktops
|
||||
AutoUpdateService services.AutoUpdateServiceGetter
|
||||
ProvisioningStates services.ProvisioningStates
|
||||
IdentityCenter services.IdentityCenter
|
||||
}
|
||||
|
||||
func (c *Config) CheckAndSetDefaults() error {
|
||||
|
||||
+9
-1
@@ -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 {
|
||||
@@ -676,6 +683,7 @@ type Services struct {
|
||||
services.StaticHostUser
|
||||
services.AutoUpdateService
|
||||
services.ProvisioningStates
|
||||
services.IdentityCenter
|
||||
}
|
||||
|
||||
// GetWebSession returns existing web session described by req.
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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: "u-test@example.com"},
|
||||
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)
|
||||
}
|
||||
@@ -49,7 +49,8 @@ func fetchEvent(t *testing.T, w types.Watcher, timeout time.Duration) types.Even
|
||||
return ev
|
||||
}
|
||||
|
||||
func testContext(t *testing.T) context.Context {
|
||||
func newTestContext(t *testing.T) context.Context {
|
||||
t.Helper()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
t.Cleanup(cancel)
|
||||
return ctx
|
||||
@@ -209,7 +210,7 @@ func TestWatchers(t *testing.T) {
|
||||
for _, test := range testCases {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := testContext(t)
|
||||
ctx := newTestContext(t)
|
||||
|
||||
// GIVEN an empty back-end
|
||||
clock := clockwork.NewFakeClock()
|
||||
|
||||
@@ -0,0 +1,399 @@
|
||||
// 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 local
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
|
||||
"github.com/gravitational/trace"
|
||||
|
||||
"github.com/gravitational/teleport"
|
||||
identitycenterv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/identitycenter/v1"
|
||||
"github.com/gravitational/teleport/api/types"
|
||||
"github.com/gravitational/teleport/lib/backend"
|
||||
"github.com/gravitational/teleport/lib/services"
|
||||
"github.com/gravitational/teleport/lib/services/local/generic"
|
||||
"github.com/gravitational/teleport/lib/utils/pagination"
|
||||
)
|
||||
|
||||
const (
|
||||
identityCenterPageSize = 100
|
||||
)
|
||||
|
||||
const (
|
||||
awsResourcePrefix = "identity_center"
|
||||
awsAccountPrefix = "accounts"
|
||||
awsPermissionSetPrefix = "permission_sets"
|
||||
awsPrincipalAssignmentPrefix = "principal_assignments"
|
||||
awsAccountAssignmentPrefix = "account_assignments"
|
||||
)
|
||||
|
||||
// IdentityCenterServiceConfig provides configuration parameters for an
|
||||
// IdentityCenterService
|
||||
type IdentityCenterServiceConfig struct {
|
||||
// Backend is the storage backend to use for the service
|
||||
Backend backend.Backend
|
||||
|
||||
// Logger is the logger for the service to use. A default will be supplied
|
||||
// if not specified.
|
||||
Logger *slog.Logger
|
||||
}
|
||||
|
||||
// CheckAndSetDefaults validates the cfg and supplies defaults where
|
||||
// appropriate.
|
||||
func (cfg *IdentityCenterServiceConfig) CheckAndSetDefaults() error {
|
||||
if cfg.Backend == nil {
|
||||
return trace.BadParameter("must supply backend")
|
||||
}
|
||||
|
||||
if cfg.Logger == nil {
|
||||
cfg.Logger = slog.Default().With(teleport.ComponentKey, "AWS-IC-LOCAL")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// IdentityCenterService handles low-level CRUD operations for the identity-
|
||||
// center related resources
|
||||
type IdentityCenterService struct {
|
||||
accounts *generic.ServiceWrapper[*identitycenterv1.Account]
|
||||
permissionSets *generic.ServiceWrapper[*identitycenterv1.PermissionSet]
|
||||
principalAssignments *generic.ServiceWrapper[*identitycenterv1.PrincipalAssignment]
|
||||
accountAssignments *generic.ServiceWrapper[*identitycenterv1.AccountAssignment]
|
||||
}
|
||||
|
||||
// compile-time assertion that the IdentityCenterService implements the
|
||||
// services.IdentityCenter interface
|
||||
var _ services.IdentityCenter = (*IdentityCenterService)(nil)
|
||||
|
||||
// NewIdentityCenterService creates a new service for managing identity-center
|
||||
// related resources
|
||||
func NewIdentityCenterService(cfg IdentityCenterServiceConfig) (*IdentityCenterService, error) {
|
||||
if err := cfg.CheckAndSetDefaults(); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
accountsSvc, err := generic.NewServiceWrapper(generic.ServiceWrapperConfig[*identitycenterv1.Account]{
|
||||
Backend: cfg.Backend,
|
||||
ResourceKind: types.KindIdentityCenterAccount,
|
||||
BackendPrefix: backend.NewKey(awsResourcePrefix, awsAccountPrefix),
|
||||
MarshalFunc: services.MarshalProtoResource[*identitycenterv1.Account],
|
||||
UnmarshalFunc: services.UnmarshalProtoResource[*identitycenterv1.Account],
|
||||
})
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err, "creating accounts service")
|
||||
}
|
||||
|
||||
permissionSetSvc, err := generic.NewServiceWrapper(generic.ServiceWrapperConfig[*identitycenterv1.PermissionSet]{
|
||||
Backend: cfg.Backend,
|
||||
ResourceKind: types.KindIdentityCenterPermissionSet,
|
||||
BackendPrefix: backend.NewKey(awsResourcePrefix, awsPermissionSetPrefix),
|
||||
MarshalFunc: services.MarshalProtoResource[*identitycenterv1.PermissionSet],
|
||||
UnmarshalFunc: services.UnmarshalProtoResource[*identitycenterv1.PermissionSet],
|
||||
})
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err, "creating permission sets service")
|
||||
}
|
||||
|
||||
principalsSvc, err := generic.NewServiceWrapper(generic.ServiceWrapperConfig[*identitycenterv1.PrincipalAssignment]{
|
||||
Backend: cfg.Backend,
|
||||
ResourceKind: types.KindIdentityCenterPrincipalAssignment,
|
||||
BackendPrefix: backend.NewKey(awsResourcePrefix, awsPrincipalAssignmentPrefix),
|
||||
MarshalFunc: services.MarshalProtoResource[*identitycenterv1.PrincipalAssignment],
|
||||
UnmarshalFunc: services.UnmarshalProtoResource[*identitycenterv1.PrincipalAssignment],
|
||||
})
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err, "creating principal assignments service")
|
||||
}
|
||||
|
||||
accountAssignmentsSvc, err := generic.NewServiceWrapper(generic.ServiceWrapperConfig[*identitycenterv1.AccountAssignment]{
|
||||
Backend: cfg.Backend,
|
||||
ResourceKind: types.KindIdentityCenterAccountAssignment,
|
||||
BackendPrefix: backend.NewKey(awsResourcePrefix, awsAccountAssignmentPrefix),
|
||||
MarshalFunc: services.MarshalProtoResource[*identitycenterv1.AccountAssignment],
|
||||
UnmarshalFunc: services.UnmarshalProtoResource[*identitycenterv1.AccountAssignment],
|
||||
})
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err, "creating account assignments service")
|
||||
}
|
||||
|
||||
svc := &IdentityCenterService{
|
||||
accounts: accountsSvc,
|
||||
permissionSets: permissionSetSvc,
|
||||
principalAssignments: principalsSvc,
|
||||
accountAssignments: accountAssignmentsSvc,
|
||||
}
|
||||
|
||||
return svc, nil
|
||||
}
|
||||
|
||||
// ListIdentityCenterAccounts provides a paged list of all AWS accounts known
|
||||
// to the Identity Center integration
|
||||
func (svc *IdentityCenterService) ListIdentityCenterAccounts(ctx context.Context, pageSize int, page *pagination.PageRequestToken) ([]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")
|
||||
}
|
||||
|
||||
accounts, nextPage, err := svc.accounts.ListResources(ctx, pageSize, pageToken)
|
||||
if err != nil {
|
||||
return nil, "", trace.Wrap(err, "listing identity center assignment records")
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
return services.IdentityCenterAccount{}, trace.Wrap(err, "creating identity center account")
|
||||
}
|
||||
return services.IdentityCenterAccount{Account: created}, nil
|
||||
}
|
||||
|
||||
// GetIdentityCenterAccount fetches a specific Identity Center Account
|
||||
func (svc *IdentityCenterService) GetIdentityCenterAccount(ctx context.Context, name services.IdentityCenterAccountID) (services.IdentityCenterAccount, error) {
|
||||
acct, err := svc.accounts.GetResource(ctx, string(name))
|
||||
if err != nil {
|
||||
return services.IdentityCenterAccount{}, trace.Wrap(err, "fetching identity center account")
|
||||
}
|
||||
return services.IdentityCenterAccount{Account: acct}, nil
|
||||
}
|
||||
|
||||
// UpdateIdentityCenterAccount performs a conditional update on an Identity
|
||||
// Center Account record, returning the updated record on success.
|
||||
func (svc *IdentityCenterService) UpdateIdentityCenterAccount(ctx context.Context, acct services.IdentityCenterAccount) (services.IdentityCenterAccount, error) {
|
||||
updated, err := svc.accounts.ConditionalUpdateResource(ctx, acct.Account)
|
||||
if err != nil {
|
||||
return services.IdentityCenterAccount{}, trace.Wrap(err, "updating identity center account record")
|
||||
}
|
||||
return services.IdentityCenterAccount{Account: updated}, nil
|
||||
}
|
||||
|
||||
// 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
|
||||
func (svc *IdentityCenterService) UpsertIdentityCenterAccount(ctx context.Context, acct services.IdentityCenterAccount) (services.IdentityCenterAccount, error) {
|
||||
updated, err := svc.accounts.UpsertResource(ctx, acct.Account)
|
||||
if err != nil {
|
||||
return services.IdentityCenterAccount{}, trace.Wrap(err, "upserting identity center account record")
|
||||
}
|
||||
return services.IdentityCenterAccount{Account: updated}, nil
|
||||
}
|
||||
|
||||
// DeleteIdentityCenterAccount deletes an Identity Center Account record
|
||||
func (svc *IdentityCenterService) DeleteIdentityCenterAccount(ctx context.Context, name services.IdentityCenterAccountID) error {
|
||||
return trace.Wrap(svc.accounts.DeleteResource(ctx, string(name)))
|
||||
}
|
||||
|
||||
// DeleteAllIdentityCenterAccounts deletes all Identity Center Account records
|
||||
func (svc *IdentityCenterService) DeleteAllIdentityCenterAccounts(ctx context.Context) error {
|
||||
return trace.Wrap(svc.accounts.DeleteAllResources(ctx))
|
||||
}
|
||||
|
||||
// ListPrincipalAssignments lists all PrincipalAssignment records in the service
|
||||
func (svc *IdentityCenterService) ListPrincipalAssignments(ctx context.Context, pageSize int, page *pagination.PageRequestToken) ([]*identitycenterv1.PrincipalAssignment, pagination.NextPageToken, error) {
|
||||
if pageSize == 0 {
|
||||
pageSize = identityCenterPageSize
|
||||
}
|
||||
|
||||
pageToken, err := page.Consume()
|
||||
if err != nil {
|
||||
return nil, "", trace.Wrap(err, "extracting page token")
|
||||
}
|
||||
|
||||
resp, nextPage, err := svc.principalAssignments.ListResources(ctx, pageSize, pageToken)
|
||||
if err != nil {
|
||||
return nil, "", trace.Wrap(err, "listing identity center assignment records")
|
||||
}
|
||||
return resp, pagination.NextPageToken(nextPage), nil
|
||||
}
|
||||
|
||||
// CreatePrincipalAssignment creates a new Principal Assignment record in the
|
||||
// service from the supplied in-memory representation. Returns the created
|
||||
// record on success.
|
||||
func (svc *IdentityCenterService) CreatePrincipalAssignment(ctx context.Context, asmt *identitycenterv1.PrincipalAssignment) (*identitycenterv1.PrincipalAssignment, error) {
|
||||
created, err := svc.principalAssignments.CreateResource(ctx, asmt)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err, "creating principal assignment")
|
||||
}
|
||||
return created, nil
|
||||
}
|
||||
|
||||
// GetPrincipalAssignment fetches a specific Principal Assignment record.
|
||||
func (svc *IdentityCenterService) GetPrincipalAssignment(ctx context.Context, name services.PrincipalAssignmentID) (*identitycenterv1.PrincipalAssignment, error) {
|
||||
state, err := svc.principalAssignments.GetResource(ctx, string(name))
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err, "fetching principal assignment")
|
||||
}
|
||||
return state, nil
|
||||
}
|
||||
|
||||
// UpdatePrincipalAssignment performs a conditional update on a Principal
|
||||
// Assignment record
|
||||
func (svc *IdentityCenterService) UpdatePrincipalAssignment(ctx context.Context, asmt *identitycenterv1.PrincipalAssignment) (*identitycenterv1.PrincipalAssignment, error) {
|
||||
updated, err := svc.principalAssignments.ConditionalUpdateResource(ctx, asmt)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err, "updating principal assignment record")
|
||||
}
|
||||
return updated, nil
|
||||
}
|
||||
|
||||
// UpsertPrincipalAssignment performs an unconditional update on a Principal
|
||||
// Assignment record
|
||||
func (svc *IdentityCenterService) UpsertPrincipalAssignment(ctx context.Context, asmt *identitycenterv1.PrincipalAssignment) (*identitycenterv1.PrincipalAssignment, error) {
|
||||
updated, err := svc.principalAssignments.UpsertResource(ctx, asmt)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err, "upserting principal assignment record")
|
||||
}
|
||||
return updated, nil
|
||||
}
|
||||
|
||||
// DeletePrincipalAssignment deletes a specific principal assignment record
|
||||
func (svc *IdentityCenterService) DeletePrincipalAssignment(ctx context.Context, name services.PrincipalAssignmentID) error {
|
||||
return trace.Wrap(svc.principalAssignments.DeleteResource(ctx, string(name)))
|
||||
}
|
||||
|
||||
// DeleteAllPrincipalAssignments deletes all assignment record
|
||||
func (svc *IdentityCenterService) DeleteAllPrincipalAssignments(ctx context.Context) error {
|
||||
return trace.Wrap(svc.principalAssignments.DeleteAllResources(ctx))
|
||||
}
|
||||
|
||||
// ListPermissionSets list the known Permission Sets in the managed Identity Center
|
||||
func (svc *IdentityCenterService) ListPermissionSets(ctx context.Context, pageSize int, page *pagination.PageRequestToken) ([]*identitycenterv1.PermissionSet, pagination.NextPageToken, error) {
|
||||
if pageSize == 0 {
|
||||
pageSize = identityCenterPageSize
|
||||
}
|
||||
pageToken, err := page.Consume()
|
||||
if err != nil {
|
||||
return nil, "", trace.Wrap(err, "extracting page token")
|
||||
}
|
||||
resp, nextPage, err := svc.permissionSets.ListResources(ctx, pageSize, pageToken)
|
||||
if err != nil {
|
||||
return nil, "", trace.Wrap(err, "listing identity center permission set records")
|
||||
}
|
||||
return resp, pagination.NextPageToken(nextPage), nil
|
||||
}
|
||||
|
||||
// CreatePermissionSet creates a new PermissionSet record based on the supplied
|
||||
// in-memory representation, returning the created record on success.
|
||||
func (svc *IdentityCenterService) CreatePermissionSet(ctx context.Context, asmt *identitycenterv1.PermissionSet) (*identitycenterv1.PermissionSet, error) {
|
||||
created, err := svc.permissionSets.CreateResource(ctx, asmt)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err, "creating identity center permission set")
|
||||
}
|
||||
return created, nil
|
||||
}
|
||||
|
||||
// GetPermissionSet fetches a specific PermissionSet record
|
||||
func (svc *IdentityCenterService) GetPermissionSet(ctx context.Context, name services.PermissionSetID) (*identitycenterv1.PermissionSet, error) {
|
||||
state, err := svc.permissionSets.GetResource(ctx, string(name))
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err, "fetching permission set")
|
||||
}
|
||||
return state, nil
|
||||
}
|
||||
|
||||
// UpdatePermissionSet performs a conditional update on the supplied Identity
|
||||
// Center Permission Set
|
||||
func (svc *IdentityCenterService) UpdatePermissionSet(ctx context.Context, asmt *identitycenterv1.PermissionSet) (*identitycenterv1.PermissionSet, error) {
|
||||
updated, err := svc.permissionSets.ConditionalUpdateResource(ctx, asmt)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err, "updating permission set record")
|
||||
}
|
||||
return updated, nil
|
||||
}
|
||||
|
||||
// DeletePermissionSet deletes a specific Identity Center PermissionSet
|
||||
func (svc *IdentityCenterService) DeletePermissionSet(ctx context.Context, name services.PermissionSetID) error {
|
||||
return trace.Wrap(svc.permissionSets.DeleteResource(ctx, string(name)))
|
||||
}
|
||||
|
||||
// ListAccountAssignments lists all IdentityCenterAccountAssignment record
|
||||
// known to the service
|
||||
func (svc *IdentityCenterService) ListAccountAssignments(ctx context.Context, pageSize int, page *pagination.PageRequestToken) ([]services.IdentityCenterAccountAssignment, pagination.NextPageToken, error) {
|
||||
if pageSize == 0 {
|
||||
pageSize = identityCenterPageSize
|
||||
}
|
||||
pageToken, err := page.Consume()
|
||||
if err != nil {
|
||||
return nil, "", trace.Wrap(err, "extracting page token")
|
||||
}
|
||||
assignments, nextPage, err := svc.accountAssignments.ListResources(ctx, pageSize, pageToken)
|
||||
if err != nil {
|
||||
return nil, "", trace.Wrap(err, "listing identity center assignment records")
|
||||
}
|
||||
|
||||
result := make([]services.IdentityCenterAccountAssignment, len(assignments))
|
||||
for i, asmt := range assignments {
|
||||
result[i] = services.IdentityCenterAccountAssignment{AccountAssignment: asmt}
|
||||
}
|
||||
|
||||
return result, pagination.NextPageToken(nextPage), nil
|
||||
}
|
||||
|
||||
// CreateAccountAssignment creates a new Account Assignment record in
|
||||
// the service from the supplied in-memory representation. Returns the
|
||||
// created record on success.
|
||||
func (svc *IdentityCenterService) CreateAccountAssignment(ctx context.Context, asmt services.IdentityCenterAccountAssignment) (services.IdentityCenterAccountAssignment, error) {
|
||||
created, err := svc.accountAssignments.CreateResource(ctx, asmt.AccountAssignment)
|
||||
if err != nil {
|
||||
return services.IdentityCenterAccountAssignment{}, trace.Wrap(err, "creating principal assignment")
|
||||
}
|
||||
return services.IdentityCenterAccountAssignment{AccountAssignment: created}, nil
|
||||
}
|
||||
|
||||
// GetAccountAssignment fetches a specific Account Assignment record.
|
||||
func (svc *IdentityCenterService) GetAccountAssignment(ctx context.Context, name services.IdentityCenterAccountAssignmentID) (services.IdentityCenterAccountAssignment, error) {
|
||||
asmt, err := svc.accountAssignments.GetResource(ctx, string(name))
|
||||
if err != nil {
|
||||
return services.IdentityCenterAccountAssignment{}, trace.Wrap(err, "fetching principal assignment")
|
||||
}
|
||||
return services.IdentityCenterAccountAssignment{AccountAssignment: asmt}, nil
|
||||
}
|
||||
|
||||
// UpdateAccountAssignment performs a conditional update on the supplied
|
||||
// Account Assignment, returning the updated record on success.
|
||||
func (svc *IdentityCenterService) UpdateAccountAssignment(ctx context.Context, asmt services.IdentityCenterAccountAssignment) (services.IdentityCenterAccountAssignment, error) {
|
||||
updated, err := svc.accountAssignments.ConditionalUpdateResource(ctx, asmt.AccountAssignment)
|
||||
if err != nil {
|
||||
return services.IdentityCenterAccountAssignment{}, trace.Wrap(err, "updating principal assignment record")
|
||||
}
|
||||
return services.IdentityCenterAccountAssignment{AccountAssignment: updated}, nil
|
||||
}
|
||||
|
||||
// DeleteAccountAssignment deletes a specific account assignment
|
||||
func (svc *IdentityCenterService) DeleteAccountAssignment(ctx context.Context, name services.IdentityCenterAccountAssignmentID) error {
|
||||
return trace.Wrap(svc.accountAssignments.DeleteResource(ctx, string(name)))
|
||||
}
|
||||
|
||||
// DeleteAllAccountAssignments deletes all known account assignments
|
||||
func (svc *IdentityCenterService) DeleteAllAccountAssignments(ctx context.Context) error {
|
||||
return trace.Wrap(svc.accountAssignments.DeleteAllResources(ctx))
|
||||
}
|
||||
@@ -0,0 +1,298 @@
|
||||
// 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 local
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/gravitational/trace"
|
||||
"github.com/jonboulle/clockwork"
|
||||
"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"
|
||||
"github.com/gravitational/teleport/lib/backend"
|
||||
"github.com/gravitational/teleport/lib/backend/lite"
|
||||
"github.com/gravitational/teleport/lib/services"
|
||||
)
|
||||
|
||||
func newTestBackend(t *testing.T, ctx context.Context, clock clockwork.Clock) backend.Backend {
|
||||
t.Helper()
|
||||
sqliteBackend, err := lite.NewWithConfig(ctx, lite.Config{
|
||||
Path: t.TempDir(),
|
||||
Clock: clock,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
require.NoError(t, sqliteBackend.Close())
|
||||
})
|
||||
return sqliteBackend
|
||||
}
|
||||
|
||||
func TestIdentityCenterResourceCRUD(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const resourceID = "alpha"
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
createResource func(*testing.T, context.Context, services.IdentityCenter, string) types.Resource153
|
||||
getResource func(context.Context, services.IdentityCenter, string) (types.Resource153, error)
|
||||
updateResource func(context.Context, services.IdentityCenter, types.Resource153) (types.Resource153, error)
|
||||
upsertResource func(context.Context, services.IdentityCenter, types.Resource153) (types.Resource153, error)
|
||||
}{
|
||||
{
|
||||
name: "Account",
|
||||
createResource: func(subtestT *testing.T, subtestCtx context.Context, svc services.IdentityCenter, id string) types.Resource153 {
|
||||
return makeTestIdentityCenterAccount(subtestT, subtestCtx, svc, id)
|
||||
},
|
||||
getResource: func(subtestCtx context.Context, svc services.IdentityCenter, id string) (types.Resource153, error) {
|
||||
return svc.GetIdentityCenterAccount(subtestCtx, services.IdentityCenterAccountID(id))
|
||||
},
|
||||
updateResource: func(subtestCtx context.Context, svc services.IdentityCenter, r types.Resource153) (types.Resource153, error) {
|
||||
acct := r.(services.IdentityCenterAccount)
|
||||
return svc.UpdateIdentityCenterAccount(subtestCtx, acct)
|
||||
},
|
||||
upsertResource: func(subtestCtx context.Context, svc services.IdentityCenter, r types.Resource153) (types.Resource153, error) {
|
||||
acct := r.(services.IdentityCenterAccount)
|
||||
return svc.UpsertIdentityCenterAccount(subtestCtx, acct)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "PermissionSet",
|
||||
createResource: func(subtestT *testing.T, subtestCtx context.Context, svc services.IdentityCenter, id string) types.Resource153 {
|
||||
return makeTestIdentityCenterPermissionSet(subtestT, subtestCtx, svc, id)
|
||||
},
|
||||
getResource: func(subtestCtx context.Context, svc services.IdentityCenter, id string) (types.Resource153, error) {
|
||||
return svc.GetPermissionSet(subtestCtx, services.PermissionSetID(id))
|
||||
},
|
||||
updateResource: func(subtestCtx context.Context, svc services.IdentityCenter, r types.Resource153) (types.Resource153, error) {
|
||||
ps := r.(*identitycenterv1.PermissionSet)
|
||||
return svc.UpdatePermissionSet(subtestCtx, ps)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "AccountAssignment",
|
||||
createResource: func(subtestT *testing.T, subtestCtx context.Context, svc services.IdentityCenter, id string) types.Resource153 {
|
||||
return makeTestIdentityCenterAccountAssignment(subtestT, subtestCtx, svc, id)
|
||||
},
|
||||
getResource: func(subtestCtx context.Context, svc services.IdentityCenter, id string) (types.Resource153, error) {
|
||||
return svc.GetAccountAssignment(subtestCtx, services.IdentityCenterAccountAssignmentID(id))
|
||||
},
|
||||
updateResource: func(subtestCtx context.Context, svc services.IdentityCenter, r types.Resource153) (types.Resource153, error) {
|
||||
asmt := r.(services.IdentityCenterAccountAssignment)
|
||||
return svc.UpdateAccountAssignment(subtestCtx, asmt)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "PrincipalAssignment",
|
||||
createResource: func(subtestT *testing.T, subtestCtx context.Context, svc services.IdentityCenter, id string) types.Resource153 {
|
||||
return makeTestIdentityCenterPrincipalAssignment(subtestT, subtestCtx, svc, id)
|
||||
},
|
||||
getResource: func(subtestCtx context.Context, svc services.IdentityCenter, id string) (types.Resource153, error) {
|
||||
return svc.GetPrincipalAssignment(subtestCtx, services.PrincipalAssignmentID(id))
|
||||
},
|
||||
updateResource: func(subtestCtx context.Context, svc services.IdentityCenter, r types.Resource153) (types.Resource153, error) {
|
||||
asmt := r.(*identitycenterv1.PrincipalAssignment)
|
||||
return svc.UpdatePrincipalAssignment(subtestCtx, asmt)
|
||||
},
|
||||
upsertResource: func(subtestCtx context.Context, svc services.IdentityCenter, r types.Resource153) (types.Resource153, error) {
|
||||
asmt := r.(*identitycenterv1.PrincipalAssignment)
|
||||
return svc.UpsertPrincipalAssignment(subtestCtx, asmt)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
t.Run("OptimisticLocking", func(t *testing.T) {
|
||||
const resourceID = "alpha"
|
||||
|
||||
ctx := newTestContext(t)
|
||||
clock := clockwork.NewFakeClock()
|
||||
backend := newTestBackend(t, ctx, clock)
|
||||
|
||||
// GIVEN an IdentityCenter service populated with a resource
|
||||
uut, err := NewIdentityCenterService(IdentityCenterServiceConfig{Backend: backend})
|
||||
require.NoError(t, err)
|
||||
createdResource := test.createResource(t, ctx, uut, resourceID)
|
||||
|
||||
// WHEN I modify the backend record for that resource...
|
||||
tmpResource, err := test.getResource(ctx, uut, resourceID)
|
||||
require.NoError(t, err)
|
||||
tmpResource.GetMetadata().Labels = map[string]string{"update": "1"}
|
||||
updatedResource, err := test.updateResource(ctx, uut, tmpResource)
|
||||
require.NoError(t, err)
|
||||
|
||||
// EXPECT that any attempt to update backend via the original in-memory
|
||||
// version of the resource fails with a comparison error
|
||||
createdResource.GetMetadata().Labels = map[string]string{"update": "2"}
|
||||
_, err = test.updateResource(ctx, uut, createdResource)
|
||||
require.True(t, trace.IsCompareFailed(err), "expected a compare failed error, got %T (%s)", err, err)
|
||||
|
||||
// EXPECT that the backend still reflects the first updated revision,
|
||||
// rather than failed update
|
||||
r, err := test.getResource(ctx, uut, resourceID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "1", r.GetMetadata().Labels["update"])
|
||||
|
||||
// WHEN I attempt update the backend via the latest revision of the
|
||||
// record...
|
||||
updatedResource.GetMetadata().Labels["update"] = "3"
|
||||
_, err = test.updateResource(ctx, uut, updatedResource)
|
||||
|
||||
// EXPECT the update to succeed, and the backend record to have been
|
||||
// updated
|
||||
require.NoError(t, err)
|
||||
r, err = test.getResource(ctx, uut, resourceID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "3", r.GetMetadata().Labels["update"])
|
||||
})
|
||||
|
||||
t.Run("UnconditionalUpsert", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if test.upsertResource == nil {
|
||||
t.Skip(test.name + " does not support unconditional upsert")
|
||||
}
|
||||
|
||||
ctx := newTestContext(t)
|
||||
clock := clockwork.NewFakeClock()
|
||||
backend := newTestBackend(t, ctx, clock)
|
||||
|
||||
// GIVEN an IdentityCenter service populated with a resource
|
||||
uut, err := NewIdentityCenterService(IdentityCenterServiceConfig{Backend: backend})
|
||||
require.NoError(t, err)
|
||||
originalResource := test.createResource(t, ctx, uut, resourceID)
|
||||
|
||||
// GIVEN that the backend record for that resource has been changed
|
||||
// between us looking up the original resource and us committing
|
||||
// any changes to it...
|
||||
tmpResource, err := test.getResource(ctx, uut, resourceID)
|
||||
require.NoError(t, err)
|
||||
tmpResource.GetMetadata().Labels = map[string]string{"update": "1"}
|
||||
_, err = test.updateResource(ctx, uut, tmpResource)
|
||||
require.NoError(t, err)
|
||||
|
||||
// WHEN I attempt to Update the modified original resource
|
||||
_, err = test.updateResource(ctx, uut, originalResource)
|
||||
|
||||
// EXPECT the Update to fail due to the changed underlying record
|
||||
require.True(t, trace.IsCompareFailed(err), "expected a compare failed error, got %T (%s)", err, err)
|
||||
|
||||
// WHEN I attempt to Upsert the modified original resource
|
||||
originalResource.GetMetadata().Labels = map[string]string{"update": "2"}
|
||||
_, err = test.upsertResource(ctx, uut, originalResource)
|
||||
|
||||
// EXPECT that an upsert will succeed, even though the underlying
|
||||
// record has changed
|
||||
require.NoError(t, err)
|
||||
|
||||
// EXPECT that the backend reflects the updated values from the
|
||||
// upsert
|
||||
r, err := test.getResource(ctx, uut, resourceID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "2", r.GetMetadata().Labels["update"])
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func makeTestIdentityCenterAccount(t *testing.T, ctx context.Context, svc services.IdentityCenter, id string) services.IdentityCenterAccount {
|
||||
t.Helper()
|
||||
created, err := svc.CreateIdentityCenterAccount(ctx, services.IdentityCenterAccount{
|
||||
Account: &identitycenterv1.Account{
|
||||
Kind: types.KindIdentityCenterAccount,
|
||||
Version: types.V1,
|
||||
Metadata: &headerv1.Metadata{Name: id},
|
||||
Spec: &identitycenterv1.AccountSpec{
|
||||
Id: "aws-account-id-" + id,
|
||||
Arn: fmt.Sprintf("arn:aws:sso::%s:", id),
|
||||
Description: "Test account " + id,
|
||||
PermissionSetInfo: []*identitycenterv1.PermissionSetInfo{
|
||||
{
|
||||
Name: "dummy",
|
||||
Arn: "arn:aws:sso:::permissionSet/ic-instance/ps-instance",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return created
|
||||
}
|
||||
|
||||
func makeTestIdentityCenterPermissionSet(t *testing.T, ctx context.Context, svc services.IdentityCenter, id string) *identitycenterv1.PermissionSet {
|
||||
t.Helper()
|
||||
created, err := svc.CreatePermissionSet(ctx, &identitycenterv1.PermissionSet{
|
||||
Kind: types.KindIdentityCenterPermissionSet,
|
||||
Version: types.V1,
|
||||
Metadata: &headerv1.Metadata{Name: id},
|
||||
Spec: &identitycenterv1.PermissionSetSpec{
|
||||
Arn: fmt.Sprintf("arn:aws:sso:::permissionSet/ic-instance/%s", id),
|
||||
Name: "aws-permission-set-" + id,
|
||||
Description: "Test permission set " + id,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return created
|
||||
}
|
||||
|
||||
func makeTestIdentityCenterAccountAssignment(t *testing.T, ctx context.Context, svc services.IdentityCenter, id string) services.IdentityCenterAccountAssignment {
|
||||
t.Helper()
|
||||
created, err := svc.CreateAccountAssignment(ctx, services.IdentityCenterAccountAssignment{
|
||||
AccountAssignment: &identitycenterv1.AccountAssignment{
|
||||
Kind: types.KindIdentityCenterAccountAssignment,
|
||||
Version: types.V1,
|
||||
Metadata: &headerv1.Metadata{Name: id},
|
||||
Spec: &identitycenterv1.AccountAssignmentSpec{
|
||||
Display: "Some-Permission-set on Some-AWS-account",
|
||||
PermissionSet: &identitycenterv1.PermissionSetInfo{
|
||||
Arn: "arn:aws:sso:::permissionSet/ic-instance/ps-instance",
|
||||
Name: "some permission set",
|
||||
},
|
||||
AccountName: "Some Account Name",
|
||||
AccountId: "some account id",
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return created
|
||||
}
|
||||
|
||||
func makeTestIdentityCenterPrincipalAssignment(t *testing.T, ctx context.Context, svc services.IdentityCenterPrincipalAssignments, id string) *identitycenterv1.PrincipalAssignment {
|
||||
t.Helper()
|
||||
created, err := svc.CreatePrincipalAssignment(ctx, &identitycenterv1.PrincipalAssignment{
|
||||
Kind: types.KindIdentityCenterPrincipalAssignment,
|
||||
Version: types.V1,
|
||||
Metadata: &headerv1.Metadata{Name: id},
|
||||
Spec: &identitycenterv1.PrincipalAssignmentSpec{
|
||||
PrincipalType: identitycenterv1.PrincipalType_PRINCIPAL_TYPE_USER,
|
||||
PrincipalId: id,
|
||||
ExternalIdSource: "scim",
|
||||
ExternalId: "some external id",
|
||||
},
|
||||
Status: &identitycenterv1.PrincipalAssignmentStatus{
|
||||
ProvisioningState: identitycenterv1.ProvisioningState_PROVISIONING_STATE_STALE,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return created
|
||||
}
|
||||
Reference in New Issue
Block a user