Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[v17] Prevent AWS OIDC integration deletion if referenced by AWS Identity Center plugin #48064

Merged
merged 4 commits into from
Oct 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading