単純な gRPC サービスがあります:
message GetUserRequest { string id = 1; } message GetUserResponse { string id = 1; string name = 2; string email = 3; } service UsersService { rpc GetUser(GetUserRequest) returns (GetUserResponse); }
gRPC は多くの点で優れており、そのパフォーマンスは素晴らしく、そのエコシステムは比較できません。
しかし、私見ですが、そのすべての最上位にあるのは、その提供される型付きコントラクトです。
私はバックエンド エンジニアです。モバイルや Web の友人と座って話し合って合意を作り、模擬実装用に Flutter と ES でスタブ クライアント コードを生成し、3 日後に再グループします。
良いパフォーマンスの一日を!
しかし、待ってください、何かが欠けています!
service UsersService { rpc GetUser(GetUserRequest) returns (GetUserResponse); }
GetUser メソッドにはほぼすべての機能が備わっていますが、承認要件を説明するにはまだ十分ではありません。
そして、RBAC ルールを記述するためのロール部分だけが欠落しています。
残念ながら、かなり近づいていますが、何か、タイプセーフな何か、汎用的な何かができれば... ?
プロト記述子の助けを借りて、あなたの願いは叶います
enum Role { ROLE_UNSPECIFIED = 0; ROLE_CUSTOMER = 1; ROLE_ADMIN = 2; } message RoleBasedAccessControl { repeated Role allowed_roles = 1; bool allow_unauthenticated = 2; } extend google.protobuf.MethodOptions { optional RoleBasedAccessControl access_control = 90000; // I don't know about this 90000, seem as long as it's unique }
service UsersService { rpc GetUser(GetUserRequest) returns (GetUserResponse) { option (components.rbac.v1.access_control) = { allowed_roles: [ ROLE_CUSTOMER, ROLE_ADMIN ] allow_unauthenticated: false }; } rpc DeleteUser(DeleteUserRequest) returns (DeleteUserResponse) { option (components.rbac.v1.access_control) = { allowed_roles: [ROLE_ADMIN] allow_unauthenticated: false }; } }
冗談ですが、夜も遅いので午前 8 時までにはオフィスに行かなければなりません?
メソッド記述子をロードします:
func RBACUnaryInterceptor(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) { // the info give us: /components.users.v1.UsersService/GetUser // we need to convert it to components.users.v1.UsersService.GetUser methodName := strings.Replace(strings.TrimPrefix(info.FullMethod, "/"), "/", ".", -1) desc, err := protoregistry.GlobalFiles.FindDescriptorByName(protoreflect.FullName(methodName)) if err != nil { return nil, status.Errorf(codes.Internal, "method not found descriptor") } method, ok := desc.(protoreflect.MethodDescriptor) if !ok { return nil, status.Errorf(codes.Internal, "some hoe this is not a method") }
access_control オプションを見つけます:
var policy *rbacv1.RoleBasedAccessControl method.Options().ProtoReflect().Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool { if fd.FullName() != rbacv1.E_AccessControl.TypeDescriptor().FullName() { // continue finding the AccessControl field return true } b, err := proto.Marshal(v.Message().Interface()) if err != nil { // TODO: better handle this as an Internal error // but for now, just return PermissionDenied return false } policy = &rbacv1.RoleBasedAccessControl{} if err := proto.Unmarshal(b, policy); err != nil { // same as above, better handle this as an Internal error return false } // btw I think this policy can be cached return false }) if policy == nil { // secure by default, DENY_ALL if no control policy is found return nil, status.Errorf(codes.PermissionDenied, "permission denied") }
新しく作成したインターセプターをバックエンドに追加または追加します
func newUsersServer() *grpc.Server { svc := grpc.NewServer(grpc.UnaryInterceptor(interceptors.RBACUnaryInterceptor)) usersv1.RegisterUsersServiceServer(svc, &usersServer{}) return svc }
次にテストします:
peasantCtx := metadata.NewOutgoingContext(context.Background(), metadata.Pairs("role", "ROLE_CUSTOMER")) _, err = client.GetUser(peasantCtx, &usersv1.GetUserRequest{}) fmt.Println(status.Code(err)) _, err = client.DeleteUser(peasantCtx, &usersv1.DeleteUserRequest{}) fmt.Println(status.Code(err)) knightlyAdminCtx := metadata.NewOutgoingContext(context.Background(), metadata.Pairs("role", "ROLE_ADMIN")) _, err = client.GetUser(knightlyAdminCtx, &usersv1.GetUserRequest{}) fmt.Println(status.Code(err)) _, err = client.DeleteUser(knightlyAdminCtx, &usersv1.DeleteUserRequest{}) fmt.Println(status.Code(err)) // Output: // OK // PermissionDenied // OK // OK
最後に、テスト可能なサンプルコードをここで公開しています https://github.com/nvcnvn/grpc-methods-descriptor-example
以上がRBAC を使用して、プロト定義に基づいて gRPC サービスを保護しますの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。