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

  1. Check the vtadmin RBAC config and add a rule granting the caller's role 'create' action on resource 'workflow' for the target cluster
  2. Re-authenticate with credentials/role that include workflow create permissions
  3. 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

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


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