Verwenden Sie RBAC, um Ihren gRPC-Dienst direkt bei der Prototypdefinition zu schützen

Mary-Kate Olsen
Freigeben: 2024-10-16 06:08:31
Original
1065 Leute haben es durchsucht

Use RBAC to protect your gRPC service right on proto definition

Wir haben einen einfachen gRPC-Dienst:

message GetUserRequest {
  string id = 1;
}

message GetUserResponse {
  string id = 1;
  string name = 2;
  string email = 3;
}

service UsersService {
  rpc GetUser(GetUserRequest) returns (GetUserResponse);
}
Nach dem Login kopieren

gRPC ist in vielerlei Hinsicht großartig, seine Leistung ist großartig, sein Ökosystem ist unvergleichlich.

Aber meiner Meinung nach gibt es darüber hinaus noch einen schriftlichen Vertrag.

Ich bin ein Backend-Ingenieur, ich und meine mobilen und Web-Freunde können uns zusammensetzen, diskutieren, eine Vereinbarung treffen, dann generieren wir den Stub-Client-Code in Flutter und Es für die Scheinimplementierung und gruppieren uns nach 3 Tagen neu.

Ein guter und leistungsorientierter Tag!

Aber Moment, uns fehlt etwas!

Welche genau Benutzerrolle kann diese API aufrufen?

service UsersService {
  rpc GetUser(GetUserRequest) returns (GetUserResponse);
}
Nach dem Login kopieren

Die GetUser-Methode hat fast alles, aber immer noch nicht genug, um die Autorisierungsanforderungen zu beschreiben.

  • Holen Sie sich: Dies ist das Verb, die Aktion
  • Benutzer: Dies ist die Ressource

Und uns fehlt nur der Rollenteil, um eine RBAC-Regel zu beschreiben.

Schade, wir kommen so nah dran, nur wenn wir etwas tun können, etwas Typsicheres, etwas Generisches ... ?

Ihr Wunsch wird mit Hilfe des Proto-Deskriptors wahr

Erweitern Sie zunächst google.protobuf.MethodOptions, um Ihre Richtlinie zu beschreiben

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
}
Nach dem Login kopieren

Verwenden Sie dann die neuen MethodOptions in Ihrer Methodendefinition (beachten Sie den Importpfad).

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
    };
  }
}
Nach dem Login kopieren

Verzweigen Sie dann den Befehl grpc-go proto generator und ändern Sie ihn

Nur ​​ein Scherz, es ist spät in der Nacht und ich muss vor 08:00 Uhr im Büro sein?

Lösung: Schreiben Sie einen Interceptor und verwenden Sie ihn mit Ihrem Server

Laden Sie den Methodendeskriptor:

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")
    }
Nach dem Login kopieren

Finden Sie unsere Option zur Zugriffskontrolle:

    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")
    }
Nach dem Login kopieren

Beispiel:

Fügen Sie den neu erstellten Interceptor zu Ihrem Backend hinzu oder hängen Sie ihn an

func newUsersServer() *grpc.Server {
    svc := grpc.NewServer(grpc.UnaryInterceptor(interceptors.RBACUnaryInterceptor))
    usersv1.RegisterUsersServiceServer(svc, &usersServer{})
    return svc
}
Nach dem Login kopieren

Dann testen Sie es:

    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
Nach dem Login kopieren

Schließlich wird hier ein Beispielcode mit testbarem Beispiel veröffentlicht: https://github.com/nvcnvn/grpc-methods-descriptor-example

Das obige ist der detaillierte Inhalt vonVerwenden Sie RBAC, um Ihren gRPC-Dienst direkt bei der Prototypdefinition zu schützen. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Quelle:dev.to
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Neueste Artikel des Autors
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage