vitessio/vitess · error · errors.ErrUnauthorized

%w: cannot delete workflow in %s

Error message

%w: cannot delete workflow in %s

What it means

The WorkflowDelete RPC checks RBAC authorization before deleting a workflow. If the caller's authenticated identity lacks DeleteAction permission on the WorkflowResource for the given cluster, the request fails with errors.ErrUnauthorized wrapped with the cluster ID. This is an access-control rejection, not a workflow-state failure.

Source

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

	response, err := vte.ExplainsAsText(plans)
	if err != nil {
		return nil, fmt.Errorf("error converting vtexplain to text output: %w", err)
	}

	return &vtadminpb.VTExplainResponse{
		Response: response,
	}, nil
}

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

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

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

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

	// Set the default options which are not supported in VTAdmin Web.
	return c.Vtctld.WorkflowDelete(ctx, req.Request)
}

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

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

View on GitHub (pinned to 01a25a7d17)

Solutions

  1. Update vtadmin's RBAC rules to grant the caller's role 'delete' on the workflow resource for that cluster ID.
  2. Confirm req.ClusterId matches the cluster ID used in the RBAC policy (exact string match).
  3. Authenticate with credentials/identity that map to an authorized role (check the identity provider and vtadmin's auth setup).
  4. Inspect the RBAC config file (`--rbac` JSON) rules array and add e.g. {"resource": "workflow", "actions": ["delete"], "clusters": ["<clusterId>"]}.
  5. If you believe access should be granted, verify with `vtadmin` logs how the IsAuthorized decision was reached.

Example fix

// before: vtadmin rbac config
[{"resource": "workflow", "actions": ["get"], "clusters": ["*"]}]
// after
[{"resource": "workflow", "actions": ["get", "delete"], "clusters": ["*"]}]
Defensive patterns

Strategy: try-catch

Validate before calling

// Client-side pre-check against the published RBAC policy
if !rbacPolicy.Allows(identity, rbac.WorkflowResource, rbac.DeleteAction, clusterID) {
    return fmt.Errorf("identity %s may not delete workflows in cluster %s", identity, clusterID)
}

Type guard

func authorized(id Identity, res rbac.Resource, act rbac.Action, cluster string) bool {
    for _, r := range id.Roles {
        for _, rule := range r.Rules {
            if rule.Matches(res, act, cluster) { return true }
        }
    }
    return false
}

Try / catch

resp, err := api.WorkflowDelete(ctx, req)
if err != nil && strings.Contains(err.Error(), "cannot delete workflow") {
    return vterrors.Errorf(vtrpcpb.Code_PERMISSION_DENIED,
        "insufficient RBAC permissions to delete workflow in cluster %s", req.ClusterId)
}

Prevention

When it happens

Trigger: Calling WorkflowDelete (API.GenerateWorkflowDeleteResponse) with req.ClusterId set to a cluster where the caller's RBAC rules do not grant workflow delete permission — e.g. rules limited to read actions or a different cluster ID.

Common situations: Read-only vtadmin user attempting a destructive operation; RBAC policy file grants permissions on cluster 'prod' but user targets 'staging' (or vice versa); missing 'delete' action in the role's rules; authz middleware not extracting identity, causing default-deny.

Related errors


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