vitessio/vitess · error · errors.ErrUnauthorized
%w: cannot switch traffic for workflow in %s
Error message
%w: cannot switch traffic for workflow in %s
What it means
VTAdmin's WorkflowSwitchTraffic endpoint rejects the request because the authenticated caller lacks the RBAC Create action permission on the Workflow resource for the given cluster. The error wraps vtadmin's sentinel errors.ErrUnauthorized so callers can errors.Is() against it. It is an authorization gate, thrown before any cluster lookup or vtctld RPC is attempted.
Source
Thrown at go/vt/vtadmin/api.go:2859
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)
if !api.authz.IsAuthorized(ctx, req.ClusterId, rbac.WorkflowResource, rbac.CreateAction) {
return nil, fmt.Errorf("%w: cannot switch traffic for workflow in %s", errors.ErrUnauthorized, req.ClusterId)
}
c, err := api.getClusterForRequest(req.ClusterId)
if err != nil {
return nil, err
}
return c.Vtctld.WorkflowSwitchTraffic(ctx, req.Request)
}
func (api *API) getClusterForRequest(id string) (*cluster.Cluster, error) {
api.clusterMu.Lock()
defer api.clusterMu.Unlock()
c, ok := api.clusterMap[id]
if !ok {
return nil, fmt.Errorf("%w: no cluster with id %s", errors.ErrUnsupportedCluster, id)
}View on GitHub (pinned to 01a25a7d17)
Solutions
- Check the vtadmin RBAC config and add a rule granting the caller's role 'create' action on resource 'workflow' for the target cluster
- Re-authenticate with credentials/role that include workflow create permissions
- If the operation should not be permitted, have a properly-privileged user perform the traffic switch
Example fix
// before (rbac config)
rules:
- resource: workflow
actions: [get, list]
// after
rules:
- resource: workflow
actions: [get, list, create] Defensive patterns
Strategy: try-catch
Validate before calling
// client-side, before calling switch traffic
if !userRolesHave(clusterID, "workflow", "create") {
return fmt.Errorf("caller lacks workflow create permission on cluster %s", clusterID)
} Try / catch
resp, err := client.WorkflowSwitchTraffic(ctx, req)
if err != nil {
if errors.Is(err, vtadminerrors.ErrUnauthorized) {
// surface a 403 / ask an admin for workflow create RBAC
return status.Error(codes.PermissionDenied, err.Error())
}
return err
} Prevention
- Mirror vtadmin RBAC rules in your client tooling so users only see permitted actions
- Grant workflow create action to operator roles that manage reshard traffic
- Test new role configs against a staging cluster id
When it happens
Trigger: Calling POST WorkflowSwitchTraffic (api.WorkflowSwitchTraffic) with credentials whose RBAC rules do not grant rbac.CreateAction on rbac.WorkflowResource for req.ClusterId.
Common situations: Users with read-only roles calling reshard/move-tables traffic-switch endpoints; misconfigured RBAC config file missing workflow create rules for the role; requests attributed to the wrong authenticated user/actor.
Understand the failure class
Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.
Related errors
- %w: cannot create schema migration in %s
- %w: cannot cancel schema migration in %s
- %w: cannot cleanup schema migration in %s
- %w: cannot complete schema migration in %s
- %w: cannot create keyspace in %s
AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01).
Data as JSON: /api/errors/622eacb1cd1dd489.
Report an issue: GitHub.