Skip to content

Commit

Permalink
[v17] Prevent AWS OIDC integration deletion if referenced by AWS Iden…
Browse files Browse the repository at this point in the history
…tity Center plugin (#48064)

* prevent aws oidc integration from deletion if referenced by ic plugin

* fixes typo and updated comment

* fixes typo

* review suggestions:
- return slice instead of pointer
- func rename
- typo fixes
  • Loading branch information
flyinghermit authored Oct 30, 2024
1 parent 60c489e commit 86154ad
Show file tree
Hide file tree
Showing 2 changed files with 116 additions and 5 deletions.
53 changes: 53 additions & 0 deletions lib/auth/integration/integrationv1/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,29 @@ func TestIntegrationCRUD(t *testing.T) {
},
ErrAssertion: trace.IsBadParameter,
},
{
Name: "can't delete integration referenced by AWS Identity Center plugin",
Role: types.RoleSpecV6{
Allow: types.RoleConditions{Rules: []types.Rule{{
Resources: []string{types.KindIntegration},
Verbs: []string{types.VerbDelete},
}}},
},
Setup: func(t *testing.T, igName string) {
_, err := localClient.CreateIntegration(ctx, sampleIntegrationFn(t, igName))
require.NoError(t, err)
require.NoError(t, localClient.CreatePlugin(ctx, newPlugin(t, igName)))
},
Test: func(ctx context.Context, resourceSvc *Service, igName string) error {
_, err := resourceSvc.DeleteIntegration(ctx, &integrationpb.DeleteIntegrationRequest{Name: igName})
return err
},
Cleanup: func(t *testing.T, igName string) {
err := localClient.DeletePlugin(ctx, newPlugin(t, igName).GetName())
require.NoError(t, err)
},
ErrAssertion: trace.IsBadParameter,
},
{
Name: "access to delete integration",
Role: types.RoleSpecV6{
Expand Down Expand Up @@ -369,6 +392,8 @@ type localClient interface {
DeleteDraftExternalAuditStorage(ctx context.Context) error
PromoteToClusterExternalAuditStorage(ctx context.Context) error
DisableClusterExternalAuditStorage(ctx context.Context) error
CreatePlugin(ctx context.Context, plugin types.Plugin) error
DeletePlugin(ctx context.Context, name string) error
}

type testClient struct {
Expand All @@ -390,6 +415,7 @@ func initSvc(t *testing.T, ca types.CertAuthority, clusterName string, proxyPubl
userSvc, err := local.NewTestIdentityService(backend)
require.NoError(t, err)
easSvc := local.NewExternalAuditStorageService(backend)
pluginSvc := local.NewPluginsService(backend)

_, err = clusterConfigSvc.UpsertAuthPreference(ctx, types.DefaultAuthPreference())
require.NoError(t, err)
Expand Down Expand Up @@ -455,11 +481,13 @@ func initSvc(t *testing.T, ca types.CertAuthority, clusterName string, proxyPubl
*local.IdentityService
*local.ExternalAuditStorageService
*local.IntegrationsService
*local.PluginsService
}{
AccessService: roleSvc,
IdentityService: userSvc,
ExternalAuditStorageService: easSvc,
IntegrationsService: localResourceService,
PluginsService: pluginSvc,
}, resourceSvc
}

Expand Down Expand Up @@ -525,3 +553,28 @@ func newCertAuthority(t *testing.T, caType types.CertAuthType, domain string) ty

return ca
}

func newPlugin(t *testing.T, integrationName string) *types.PluginV1 {
t.Helper()
return &types.PluginV1{
Metadata: types.Metadata{
Name: types.PluginTypeAWSIdentityCenter,
Labels: map[string]string{
types.HostedPluginLabel: "true",
},
},
Spec: types.PluginSpecV1{
Settings: &types.PluginSpecV1_AwsIc{
AwsIc: &types.PluginAWSICSettings{
IntegrationName: integrationName,
Region: "test-region",
Arn: "test-arn",
AccessListDefaultOwners: []string{"user1", "user2"},
ProvisioningSpec: &types.AWSICProvisioningSpec{
BaseUrl: "https://example.com",
},
},
},
},
}
}
68 changes: 63 additions & 5 deletions lib/services/local/integrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,22 +127,44 @@ func (s *IntegrationsService) DeleteIntegration(ctx context.Context, name string
return trace.Wrap(err)
}

conditionalActions, err := notReferencedByEAS(ctx, s.backend, name)
deleteConditions, err := integrationDeletionConditions(ctx, s.backend, name)
if err != nil {
return trace.Wrap(err)
}
conditionalActions = append(conditionalActions, backend.ConditionalAction{
deleteConditions = append(deleteConditions, backend.ConditionalAction{
Key: s.svc.MakeKey(backend.NewKey(name)),
Condition: backend.Exists(),
Action: backend.Delete(),
})
_, err = s.backend.AtomicWrite(ctx, conditionalActions)
_, err = s.backend.AtomicWrite(ctx, deleteConditions)
return trace.Wrap(err)
}

// notReferencedByEAS returns a slice of ConditionalActions to use with a backend.AtomicWrite to ensure that
// integrationDeletionConditions returns a BadParameter error if the integration is referenced by another
// Teleport service. If it does not find any direct reference, the backend.ConditionalAction is returned
// with the current state of reference, which should be added to AtomicWrite to ensure that the current
// reference state remains unchanged until the integration is completely deleted.
// Service may have zero or multiple ConditionalActions returned.
func integrationDeletionConditions(ctx context.Context, bk backend.Backend, name string) ([]backend.ConditionalAction, error) {
var deleteConditionalActions []backend.ConditionalAction
easDeleteConditions, err := integrationReferencedByEAS(ctx, bk, name)
if err != nil {
return nil, trace.Wrap(err)
}
deleteConditionalActions = append(deleteConditionalActions, easDeleteConditions...)

awsIcDeleteCondition, err := integrationReferencedByAWSICPlugin(ctx, bk, name)
if err != nil {
return nil, trace.Wrap(err)
}
deleteConditionalActions = append(deleteConditionalActions, awsIcDeleteCondition...)

return deleteConditionalActions, nil
}

// integrationReferencedByEAS returns a slice of ConditionalActions to use with a backend.AtomicWrite to ensure that
// integration [name] is not referenced by any EAS (External Audit Storage) integration.
func notReferencedByEAS(ctx context.Context, bk backend.Backend, name string) ([]backend.ConditionalAction, error) {
func integrationReferencedByEAS(ctx context.Context, bk backend.Backend, name string) ([]backend.ConditionalAction, error) {
var conditionalActions []backend.ConditionalAction
for _, key := range []backend.Key{draftExternalAuditStorageBackendKey, clusterExternalAuditStorageBackendKey} {
condition := backend.ConditionalAction{
Expand Down Expand Up @@ -173,6 +195,42 @@ func notReferencedByEAS(ctx context.Context, bk backend.Backend, name string) ([
return conditionalActions, nil
}

// integrationReferencedByAWSICPlugin returns an error if the integration name is referenced
// by an existing AWS Identity Center plugin. In case the AWS Identity Center plugin exists
// but does not reference this integration, a conditional action is returned with a revision
// of the plugin to ensure that plugin hasn't changed during deletion of the AWS OIDC integration.
func integrationReferencedByAWSICPlugin(ctx context.Context, bk backend.Backend, name string) ([]backend.ConditionalAction, error) {
var conditionalActions []backend.ConditionalAction
pluginService := NewPluginsService(bk)
plugins, err := pluginService.GetPlugins(ctx, false)
if err != nil {
return nil, trace.Wrap(err)
}

for _, p := range plugins {
pluginV1, ok := p.(*types.PluginV1)
if !ok {
continue
}

if pluginV1.GetType() == types.PluginType(types.PluginTypeAWSIdentityCenter) {
switch pluginV1.Spec.GetAwsIc().IntegrationName {
case name:
return nil, trace.BadParameter("cannot delete AWS OIDC integration currently referenced by AWS Identity Center integration %q", pluginV1.GetName())
default:
conditionalActions = append(conditionalActions, backend.ConditionalAction{
Key: backend.NewKey(pluginsPrefix, name),
Action: backend.Nop(),
Condition: backend.Revision(pluginV1.GetRevision()),
})
return conditionalActions, nil
}
}
}

return conditionalActions, nil
}

// DeleteAllIntegrations removes all Integration resources. This should only be used in a cache.
func (s *IntegrationsService) DeleteAllIntegrations(ctx context.Context) error {
if !s.cacheMode {
Expand Down

0 comments on commit 86154ad

Please sign in to comment.