>백엔드 개발 >Golang >RBAC를 사용하여 proto 정의에서 바로 gRPC 서비스를 보호하세요.

RBAC를 사용하여 proto 정의에서 바로 gRPC 서비스를 보호하세요.

Mary-Kate Olsen
Mary-Kate Olsen원래의
2024-10-16 06:08:311105검색

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

그런 다음 grpc-go proto 생성기 명령을 분기하고 수정합니다.

농담입니다. 밤이 늦어서 오전 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를 사용하여 proto 정의에서 바로 gRPC 서비스를 보호하세요.의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
이전 기사:GO 학습 : - 루프다음 기사:GO 학습 : - 루프