ホームページ  >  記事  >  バックエンド開発  >  RBAC を使用して、プロト定義に基づいて gRPC サービスを保護します

RBAC を使用して、プロト定義に基づいて gRPC サービスを保護します

Mary-Kate Olsen
Mary-Kate Olsenオリジナル
2024-10-16 06:08:311063ブラウズ

Use RBAC to protect your gRPC service right on proto definition

単純な 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 日後に再グループします。

良いパフォーマンスの一日を!

しかし、待ってください、何かが欠けています!

この API を呼び出すことができるユーザーの役割は何ですか?

service UsersService {
  rpc GetUser(GetUserRequest) returns (GetUserResponse);
}

GetUser メソッドにはほぼすべての機能が備わっていますが、承認要件を説明するにはまだ十分ではありません。

  • 取得: これは動詞、アクションです
  • ユーザー: これはリソースです

そして、RBAC ルールを記述するためのロール部分だけが欠落しています。

残念ながら、かなり近づいていますが、何か、タイプセーフな何か、汎用的な何かができれば... ?

プロト記述子の助けを借りて、あなたの願いは叶います

まず、google.protobuf.MethodOptions を拡張してポリシーを記述します。

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
}

次に、メソッド定義で新しい MethodOptions を使用します (インポート パスに注意してください)。

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
    };
  }
}

次に、grpc-go protogenerator コマンドをフォークして変更します。

冗談ですが、夜も遅いので午前 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 サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。