{"id":18832645,"url":"https://github.com/linka-cloud/grpc-rbac","last_synced_at":"2026-01-25T18:30:17.819Z","repository":{"id":39491636,"uuid":"506705201","full_name":"linka-cloud/grpc-rbac","owner":"linka-cloud","description":null,"archived":false,"fork":false,"pushed_at":"2023-03-08T15:02:48.000Z","size":79,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2024-06-22T14:06:47.533Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/linka-cloud.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-06-23T16:02:35.000Z","updated_at":"2023-03-23T06:39:23.000Z","dependencies_parsed_at":"2024-06-21T12:51:00.899Z","dependency_job_id":"79a689ed-ba26-4f1e-a113-20529bcfe0ff","html_url":"https://github.com/linka-cloud/grpc-rbac","commit_stats":null,"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/linka-cloud%2Fgrpc-rbac","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/linka-cloud%2Fgrpc-rbac/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/linka-cloud%2Fgrpc-rbac/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/linka-cloud%2Fgrpc-rbac/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/linka-cloud","download_url":"https://codeload.github.com/linka-cloud/grpc-rbac/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":239768927,"owners_count":19693763,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-08T01:58:36.389Z","updated_at":"2026-01-25T18:30:17.752Z","avatar_url":"https://github.com/linka-cloud.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# grpc-rbac\n\n*This project is currently in **alpha**. The API should be considered unstable and likely to change*\n\n\n**grpc-rbac** is a minimalist wrapper around [go-rbac (v2)](https://github.com/mikespook/gorbac) which provide\na small **rbac** engine and *gRPC interceptors*. \n\n**protoc-gen-go-rbac** is a protoc plugin generating the service permissions and a method to register permissions\nto the **rbac** engine.\n\n### Install\n\n```bash\ngo get -v go.linka.cloud/grpc-rbac\ngo install go.linka.cloud/grpc-rbac/cmd/protoc-gen-go-rbac\n```\n\n### Import\n\n```go\nimport grpc_rbac \"go.linka.cloud/grpc-rbac\"\n```\n\n### Usage\n\nUse the plugin as any other **protoc** plugins.\n\n### Generated code\n\nFor a given **example.proto**:\n\n```protobuf\nsyntax = \"proto3\";\n\npackage example;\n\noption go_package = \"go.linka.cloud/grpc-rbac/example\";\n\nimport \"rbac/rbac.proto\";\n\nmessage Resource {\n  string id = 1;\n}\n\nservice ResourceService {\n  option(rbac.def) = {\n    roles: [{\n      name: \"admin\",\n      parents: [\"writer\", \"reader\", \"watcher\"],\n    }],\n  };\n  rpc Create(CreateRequest) returns (CreateResponse) {\n    option (rbac.access) = {\n      roles: [\"writer\"]\n    };\n  }\n  rpc Read(ReadRequest) returns (ReadResponse) {\n    option (rbac.access) = {\n      roles: [\"reader\"]\n    };\n  }\n  rpc Update(UpdateRequest) returns (UpdateResponse) {\n    option (rbac.access) = {\n      roles: [\"writer\"]\n    };\n  };\n  rpc Delete(DeleteRequest) returns (DeleteResponse) {\n    option (rbac.access) = {\n      roles: [\"writer\"]\n    };\n  };\n  rpc List(ListRequest) returns (ListResponse) {\n    option (rbac.access) = {\n      roles: [\"reader\"]\n    };\n  };\n  rpc Watch(WatchRequest) returns (stream Event) {\n    option (rbac.access) = {\n      roles: [\"watcher\"]\n    };\n  };\n}\n\n// ... requests, responses and event definitions ...\n```\n\nThe following **example.pb.rbac.go** will be generated:\n\n```go\n// Copyright 2022 Linka Cloud  All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Code generated by protoc-gen-go-rbac. DO NOT EDIT.\npackage example\n\nimport (\n\tgrpc_rbac \"go.linka.cloud/grpc-rbac\"\n)\n\nvar ResourceServicePermissions = struct {\n\tCreate grpc_rbac.Permission\n\tRead   grpc_rbac.Permission\n\tUpdate grpc_rbac.Permission\n\tDelete grpc_rbac.Permission\n\tList   grpc_rbac.Permission\n\tWatch  grpc_rbac.Permission\n}{\n\tCreate: grpc_rbac.NewGRPCPermission(\"example.ResourceService\", \"Create\"),\n\tRead:   grpc_rbac.NewGRPCPermission(\"example.ResourceService\", \"Read\"),\n\tUpdate: grpc_rbac.NewGRPCPermission(\"example.ResourceService\", \"Update\"),\n\tDelete: grpc_rbac.NewGRPCPermission(\"example.ResourceService\", \"Delete\"),\n\tList:   grpc_rbac.NewGRPCPermission(\"example.ResourceService\", \"List\"),\n\tWatch:  grpc_rbac.NewGRPCPermission(\"example.ResourceService\", \"Watch\"),\n}\n\nvar ResourceServiceRoles = struct {\n\tAdmin   *grpc_rbac.StdRole\n\tReader  *grpc_rbac.StdRole\n\tWatcher *grpc_rbac.StdRole\n\tWriter  *grpc_rbac.StdRole\n}{\n\tAdmin:   grpc_rbac.NewStdRole(\"ResourceService.Admin\"),\n\tReader:  grpc_rbac.NewStdRole(\"ResourceService.Reader\"),\n\tWatcher: grpc_rbac.NewStdRole(\"ResourceService.Watcher\"),\n\tWriter:  grpc_rbac.NewStdRole(\"ResourceService.Writer\"),\n}\n\nfunc RegisterResourceServicePermissions(rbac grpc_rbac.RBAC) {\n\t// Register Admin role\n\tif err := rbac.Add(ResourceServiceRoles.Admin); err != nil {\n\t\tpanic(err)\n\t}\n\n\t// Assign Reader permissions\n\tif err := ResourceServiceRoles.Reader.Assign(ResourceServicePermissions.Read); err != nil {\n\t\tpanic(err)\n\t}\n\tif err := ResourceServiceRoles.Reader.Assign(ResourceServicePermissions.List); err != nil {\n\t\tpanic(err)\n\t}\n\t// Register Reader role\n\tif err := rbac.Add(ResourceServiceRoles.Reader); err != nil {\n\t\tpanic(err)\n\t}\n\n\t// Assign Watcher permissions\n\tif err := ResourceServiceRoles.Watcher.Assign(ResourceServicePermissions.Watch); err != nil {\n\t\tpanic(err)\n\t}\n\t// Register Watcher role\n\tif err := rbac.Add(ResourceServiceRoles.Watcher); err != nil {\n\t\tpanic(err)\n\t}\n\n\t// Assign Writer permissions\n\tif err := ResourceServiceRoles.Writer.Assign(ResourceServicePermissions.Create); err != nil {\n\t\tpanic(err)\n\t}\n\tif err := ResourceServiceRoles.Writer.Assign(ResourceServicePermissions.Update); err != nil {\n\t\tpanic(err)\n\t}\n\tif err := ResourceServiceRoles.Writer.Assign(ResourceServicePermissions.Delete); err != nil {\n\t\tpanic(err)\n\t}\n\t// Register Writer role\n\tif err := rbac.Add(ResourceServiceRoles.Writer); err != nil {\n\t\tpanic(err)\n\t}\n\n\t// Assign Admin parents\n\tif err := rbac.SetParent(ResourceServiceRoles.Admin.ID(), ResourceServiceRoles.Writer.ID()); err != nil {\n\t\tpanic(err)\n\t}\n\tif err := rbac.SetParent(ResourceServiceRoles.Admin.ID(), ResourceServiceRoles.Reader.ID()); err != nil {\n\t\tpanic(err)\n\t}\n\tif err := rbac.SetParent(ResourceServiceRoles.Admin.ID(), ResourceServiceRoles.Watcher.ID()); err != nil {\n\t\tpanic(err)\n\t}\n\n\t// Register ResourceService Service rules\n\trbac.Register(\u0026ResourceService_ServiceDesc)\n}\n\n```\n\n### Usage\n\nSee [the example directory](./example/) for complete example.\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"log\"\n\t\"time\"\n\n\t\"github.com/fullstorydev/grpchan/inprocgrpc\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/metadata\"\n\t\"google.golang.org/grpc/status\"\n\n\tgrbac \"go.linka.cloud/grpc-rbac\"\n\texample \"go.linka.cloud/grpc-rbac/example/pb\"\n)\n\nconst (\n\t// roleKey is the metadata key where roles are stored\n\troleKey = \"roles\"\n)\n\n// rbacCtx set role in request outgoing metadata\nfunc rbacCtx(ctx context.Context, role grbac.Role) context.Context {\n\treturn metadata.AppendToOutgoingContext(ctx, roleKey, role.ID())\n}\n\nfunc main() {\n\tctx, cancel := context.WithCancel(context.Background())\n\tdefer cancel()\n\n\t// create the rbac engine with our metadata role extraction function\n\trbac := grbac.New(grbac.WithRoleFunc(func(ctx context.Context) ([]grbac.Role, error) {\n\t\tmd, ok := metadata.FromIncomingContext(ctx)\n\t\tif !ok {\n\t\t\treturn nil, status.Errorf(codes.Unauthenticated, \"missing role from metadata\")\n\t\t}\n\t\tvar roles []grbac.Role\n\t\tfor _, v := range md.Get(roleKey) {\n\t\t\tlog.Printf(\"role: %s\", v)\n\t\t\troles = append(roles, grbac.NewStdRole(v))\n\t\t}\n\t\treturn roles, nil\n\t}))\n\n\t// create the service\n\tsvc := NewResourceService()\n\n\t// register the service permissions\n\texample.RegisterResourceServicePermissions(rbac)\n\n\t// create the in-process grpc channel\n\tchannel := (\u0026inprocgrpc.Channel{}).\n\t\tWithServerUnaryInterceptor(rbac.UnaryServerInterceptor()).\n\t\tWithServerStreamInterceptor(rbac.StreamServerInterceptor())\n\n\t// register the service as usual\n\texample.RegisterResourceServiceServer(channel, svc)\n\n\t// create a client\n\tclient := example.NewResourceServiceClient(channel)\n\n\t// validate checks\n\tif _, err := client.List(rbacCtx(ctx, example.ResourceServiceRoles.Reader), \u0026example.ListRequest{}); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tif _, err := client.Create(rbacCtx(ctx, example.ResourceServiceRoles.Reader), \u0026example.CreateRequest{Payload: \u0026example.Resource{Id: \"0\"}}); err == nil {\n\t\tlog.Fatal(\"reader should not be able to create\")\n\t}\n\n\tif _, err := client.Create(rbacCtx(ctx, example.ResourceServiceRoles.Writer), \u0026example.CreateRequest{Payload: \u0026example.Resource{Id: \"0\"}}); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tss, err := client.Watch(rbacCtx(ctx, example.ResourceServiceRoles.Writer), \u0026example.WatchRequest{})\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\t// try to receive an event as the interceptor is not called when creating the stream\n\tif _, err := ss.Recv(); err == nil {\n\t\tlog.Fatal(\"writer should not be able to watch\")\n\t}\n\n\tss, err = client.Watch(rbacCtx(ctx, example.ResourceServiceRoles.Admin), \u0026example.WatchRequest{})\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\t// create a resource to trigger an event\n\tgo func() {\n\t\ttime.Sleep(100 * time.Millisecond)\n\t\tif _, err := client.Create(rbacCtx(ctx, example.ResourceServiceRoles.Writer), \u0026example.CreateRequest{Payload: \u0026example.Resource{Id: \"1\"}}); err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t}()\n\tif _, err := ss.Recv(); err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flinka-cloud%2Fgrpc-rbac","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flinka-cloud%2Fgrpc-rbac","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flinka-cloud%2Fgrpc-rbac/lists"}