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
- Grant 'delete' on the keyspace resource to the operator's role in the RBAC config (or use a dedicated admin role for destructive ops)
- Re-authenticate after role changes so the new claims are used
- 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
- Treat destructive actions as separate grants; never rely on create implying delete
- Run deletion jobs with a dedicated admin service account
- Confirm token freshness after role changes
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
- %w: cannot create keyspace in %s
- %w: cannot delete shards in %s
- %w: cannot create schema migration in %s
- %w: cannot cancel schema migration in %s
- %w: cannot cleanup schema migration in %s
AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01).
Data as JSON: /api/errors/381763367cec6a72.
Report an issue: GitHub.