{"record":{"id":"622eacb1cd1dd489","repo":"vitessio/vitess","slug":"w-cannot-switch-traffic-for-workflow-in-s","errorCode":null,"errorMessage":"%w: cannot switch traffic for workflow in %s","messagePattern":"%w: cannot switch traffic for workflow in (.+?)","errorType":"error_code","errorClass":"errors.ErrUnauthorized","httpStatus":null,"severity":"error","filePath":"go/vt/vtadmin/api.go","lineNumber":2859,"sourceCode":"\n\tc, err := api.getClusterForRequest(req.ClusterId)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Set the default options which are not supported in VTAdmin Web.\n\treturn c.Vtctld.WorkflowDelete(ctx, req.Request)\n}\n\n// WorkflowSwitchTraffic is part of the vtadminpb.VTAdminServer interface.\nfunc (api *API) WorkflowSwitchTraffic(ctx context.Context, req *vtadminpb.WorkflowSwitchTrafficRequest) (*vtctldatapb.WorkflowSwitchTrafficResponse, error) {\n\tspan, ctx := trace.NewSpan(ctx, \"API.WorkflowSwitchTraffic\")\n\tdefer span.Finish()\n\n\tspan.Annotate(\"cluster_id\", req.ClusterId)\n\n\tif !api.authz.IsAuthorized(ctx, req.ClusterId, rbac.WorkflowResource, rbac.CreateAction) {\n\t\treturn nil, fmt.Errorf(\"%w: cannot switch traffic for workflow in %s\", errors.ErrUnauthorized, req.ClusterId)\n\t}\n\n\tc, err := api.getClusterForRequest(req.ClusterId)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn c.Vtctld.WorkflowSwitchTraffic(ctx, req.Request)\n}\n\nfunc (api *API) getClusterForRequest(id string) (*cluster.Cluster, error) {\n\tapi.clusterMu.Lock()\n\tdefer api.clusterMu.Unlock()\n\n\tc, ok := api.clusterMap[id]\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"%w: no cluster with id %s\", errors.ErrUnsupportedCluster, id)\n\t}","sourceCodeStart":2841,"sourceCodeEnd":2877,"githubUrl":"https://github.com/vitessio/vitess/blob/01a25a7d176f94613b8d59d799f438380a8760e4/go/vt/vtadmin/api.go#L2841-L2877","documentation":"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.","triggerScenarios":"Calling POST WorkflowSwitchTraffic (api.WorkflowSwitchTraffic) with credentials whose RBAC rules do not grant rbac.CreateAction on rbac.WorkflowResource for req.ClusterId.","commonSituations":"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.","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"],"exampleFix":"// before (rbac config)\nrules:\n  - resource: workflow\n    actions: [get, list]\n// after\nrules:\n  - resource: workflow\n    actions: [get, list, create]","handlingStrategy":"try-catch","validationCode":"// client-side, before calling switch traffic\nif !userRolesHave(clusterID, \"workflow\", \"create\") {\n    return fmt.Errorf(\"caller lacks workflow create permission on cluster %s\", clusterID)\n}","typeGuard":null,"tryCatchPattern":"resp, err := client.WorkflowSwitchTraffic(ctx, req)\nif err != nil {\n    if errors.Is(err, vtadminerrors.ErrUnauthorized) {\n        // surface a 403 / ask an admin for workflow create RBAC\n        return status.Error(codes.PermissionDenied, err.Error())\n    }\n    return err\n}","preventionTips":["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"],"tags":["rbac","authorization","vtadmin"],"backgroundTag":"permission-denied","analyzedSha":"01a25a7d176f94613b8d59d799f438380a8760e4","analyzedAt":"2026-09-01T17:28:30.605Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}