首页  >  文章  >  后端开发  >  使用 RBAC 保护您的 gRPC 服务的原始定义

使用 RBAC 保护您的 gRPC 服务的原始定义

Mary-Kate Olsen
Mary-Kate Olsen原创
2024-10-16 06:08:31969浏览

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 在很多方面都很棒,它的性能很棒,它的生态系统是无法比较的。

但恕我直言,最重要的是它提供的打字合同。

我是一名后端工程师,我和我的移动和网络朋友可以坐下来讨论,达成协议,然后我们在 flutter 和 es 中生成存根客户端代码进行模拟实现,3天后重新组合。

美好而有表现的一天!

但是等等,我们漏掉了一些东西!

到底什么用户角色可以调用这个API?

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

GetUser方法几乎具备了一切,但还不足以描述授权需求。

  • Get:这是动词,动作
  • 用户:这是资源

我们只缺少角色部分来描述 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
    };
  }
}

然后fork并修改grpc-go proto生成器命令

开个玩笑,夜深了,我需要在 08:00 AM 之前到达办公室?

解决方案:编写一个拦截器并将其与您的服务器一起使用

加载方法描述符:

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中文网其他相关文章!

声明:
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn