Maison >développement back-end >Golang >Utilisez RBAC pour protéger votre service gRPC directement lors de la définition du proto

Utilisez RBAC pour protéger votre service gRPC directement lors de la définition du proto

Mary-Kate Olsen
Mary-Kate Olsenoriginal
2024-10-16 06:08:311105parcourir

Use RBAC to protect your gRPC service right on proto definition

Nous avons un service gRPC simple :

message GetUserRequest {
  string id = 1;
}

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

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

gRPC est excellent à bien des égards, ses performances sont excellentes, son écosystème est incomparable.

Mais à mon humble avis, par-dessus tout cela se trouve un contrat dactylographié qu'il fournit.

Je suis un ingénieur backend, moi et mes amis mobiles et Web pouvons nous asseoir, discuter, parvenir à un accord, puis nous générons le code client stub en flutter et es pour une implémentation simulée, nous regroupons après 3 jours.

Une bonne journée de performance !

Mais attendez, il nous manque quelque chose !

Quel rôle d'utilisateur exactement peut appeler cette API ?

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

La méthode GetUser a presque tout, mais toujours pas assez pour décrire les exigences d'autorisation.

  • Obtenir : c'est le verbe, l'action
  • Utilisateur : c'est la ressource

Et il ne nous manque que la partie Rôle pour décrire une règle RBAC.

Trop dommage, nous sommes si proches, juste si nous pouvons faire quelque chose, quelque chose de type sécurisé, quelque chose de générique... ?

Votre souhait se réalisera, avec l'aide du proto descripteur

Tout d’abord, étendez google.protobuf.MethodOptions pour décrire votre stratégie.

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
}

Ensuite, utilisez les nouvelles MethodOptions dans votre définition de méthodes (faites attention au chemin d'importation)

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

Ensuite, forkez et modifiez la commande du générateur de proto grpc-go

Je plaisante, la nuit est tard et je dois être au bureau avant 08h00 ?

Solution : écrivez un intercepteur et utilisez-le avec votre serveur

Charger le descripteur de méthodes :

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")
    }

Retrouvez notre option 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")
    }

Exemple:

Ajoutez ou ajoutez l'intercepteur nouvellement créé à votre backend

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

Alors testez-le :

    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

Enfin, exemple de code avec exemple testable publié ici https://github.com/nvcnvn/grpc-methods-descriptor-example

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Article précédent:Apprentissage GO : - BouclesArticle suivant:Apprentissage GO : - Boucles