vitessio/vitess · error · ErrUnauthorized

%w: cannot delete keyspace in %s

Error message

%w: cannot delete keyspace in %s

What it means

VTAdmin's DeleteKeyspace RPC returns this when the caller's role lacks the 'delete' action on the Keyspace resource for the given cluster. The request is rejected before any vtctld interaction, wrapping errors.ErrUnauthorized. Deletion is intentionally a separate, higher-privilege action from creation.

Source

Thrown at go/vt/vtadmin/api.go:653

	}

	c, err := api.getClusterForRequest(req.ClusterId)
	if err != nil {
		return nil, err
	}

	return c.CreateShard(ctx, req.Options)
}

// DeleteKeyspace is part of the vtadminpb.VTAdminServer interface.
func (api *API) DeleteKeyspace(ctx context.Context, req *vtadminpb.DeleteKeyspaceRequest) (*vtctldatapb.DeleteKeyspaceResponse, error) {
	span, ctx := trace.NewSpan(ctx, "API.DeleteKeyspace")
	defer span.Finish()

	span.Annotate("cluster_id", req.ClusterId)

	if !api.authz.IsAuthorized(ctx, req.ClusterId, rbac.KeyspaceResource, rbac.DeleteAction) {
		return nil, fmt.Errorf("%w: cannot delete keyspace in %s", errors.ErrUnauthorized, req.ClusterId)
	}

	c, err := api.getClusterForRequest(req.ClusterId)
	if err != nil {
		return nil, err
	}

	return c.DeleteKeyspace(ctx, req.Options)
}

// DeleteShards is part of the vtadminpb.VTAdminServer interface.
func (api *API) DeleteShards(ctx context.Context, req *vtadminpb.DeleteShardsRequest) (*vtctldatapb.DeleteShardsResponse, error) {
	span, ctx := trace.NewSpan(ctx, "API.DeleteShards")
	defer span.Finish()

	span.Annotate("cluster_id", req.ClusterId)

	if !api.authz.IsAuthorized(ctx, req.ClusterId, rbac.ShardResource, rbac.DeleteAction) {

View on GitHub (pinned to 01a25a7d17)

Solutions

  1. Grant 'delete' on the keyspace resource to the operator's role in the RBAC config (or use a dedicated admin role for destructive ops)
  2. Re-authenticate after role changes so the new claims are used
  3. Verify the cluster ID matches the role scope

Example fix

// before
  - resource: keyspace
    actions: [get, create]
// after
  - resource: keyspace
    actions: [get, create, delete]
Defensive patterns

Strategy: validation

Validate before calling

const canDeleteKeyspace = permissions.some(rule => rule.resource === 'keyspace' && (rule.actions.includes('delete') || rule.actions.includes('*')) && (rule.clusters.includes(clusterId) || rule.clusters.includes('*')));
if (!canDeleteKeyspace) throw new Error('RBAC denies keyspace delete in ' + clusterId);

Type guard

function isKeyspaceDeleteDenied(err: unknown): boolean {
  return err instanceof Error && err.message.includes('cannot delete keyspace');
}

Try / catch

try {
  await deleteKeyspace(clusterId, ks, recursive);
} catch (err) {
  if (String(err).includes('cannot delete keyspace')) {
    requestAdminApproval(clusterId, ks);
  } else {
    throw err;
  }
}

Prevention

When it happens

Trigger: Calling DeleteKeyspace (DELETE /keyspace) where RBAC does not grant keyspace delete for req.ClusterId.

Common situations: Cleanup scripts running under accounts with create-but-not-delete permissions; production clusters scoped to stricter roles than dev; stale auth tokens representing old role assignments.

Related errors


AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01). Data as JSON: /api/errors/381763367cec6a72. Report an issue: GitHub.