Communication entre processus avec gRPC

Les processus exécutés sur le même ordinateur peuvent être conçus pour communiquer entre eux. Les systèmes d’exploitation fournissent des technologies permettant une communication entre processus (IPC) rapide et efficace. Les sockets de domaine Unix et les canaux nommés sont des exemples populaires de technologies IPC.

.NET prend en charge la communication entre processus à l’aide de gRPC.

La prise en charge intégrée des canaux nommés dans ASP.NET Core nécessite .NET 8 ou version ultérieure.

Prise en main

Les appels IPC sont envoyés d’un client à un serveur. Pour communiquer entre les applications sur un ordinateur avec gRPC, au moins une application doit héberger un serveur gRPC ASP.NET Core.

Un serveur gRPC principal ASP.NET est généralement créé à partir du modèle gRPC. Le fichier projet créé par le modèle utilise Microsoft.NET.SDK.Web comme SDK :

<Project Sdk="Microsoft.NET.Sdk.Web">

  <ItemGroup>
    <PackageReference Include="Grpc.AspNetCore" Version="2.47.0" />
    <Protobuf Include="Protos\greet.proto" GrpcServices="Server" />
  </ItemGroup>

</Project>

La valeur du Kit de développement logiciel (SDK) Microsoft.NET.SDK.Web ajoute automatiquement une référence à l’infrastructure ASP.NET Core. La référence permet à l’application d’utiliser ASP.NET types Core requis pour héberger un serveur.

Il est également possible d'ajouter un serveur à des projets non-ASP.NET Core existants, tels que les services Windows, les applications WPF ou les applications WinForms. Consultez Héberger gRPC dans des projets non-ASP.NET Core pour plus d’informations.

Transports de communication entre processus (IPC)

Les appels gRPC entre un client et un serveur sur différents ordinateurs sont généralement envoyés via des sockets TCP. TCP est un bon choix pour communiquer sur un réseau ou sur Internet. Cependant, les transports IPC offrent des avantages lors de la communication entre des processus sur la même machine :

  • Moins de travail supplémentaire et des vitesses de transfert plus rapides.
  • Intégration aux fonctionnalités de sécurité du système d’exploitation.
  • N’utilise pas de ports TCP, qui sont une ressource limitée.

.NET prend en charge plusieurs transports IPC :

Selon le système d’exploitation, les applications multiplateformes peuvent choisir différents transports IPC. Une application peut vérifier le système d’exploitation au démarrage et choisir le transport souhaité pour cette plateforme :

var builder = WebApplication.CreateBuilder(args);
builder.WebHost.ConfigureKestrel(serverOptions =>
{
    if (OperatingSystem.IsWindows())
    {
        serverOptions.ListenNamedPipe("MyPipeName");
    }
    else
    {
        var socketPath = Path.Combine(Path.GetTempPath(), "socket.tmp");
        serverOptions.ListenUnixSocket(socketPath);
    }

    serverOptions.ConfigureEndpointDefaults(listenOptions =>
    {
        listenOptions.Protocols = HttpProtocols.Http2;
    });
});

Considérations de sécurité

Les applications IPC envoient et reçoivent des appels RPC. La communication externe est un vecteur d’attaque potentiel pour les applications IPC et doit être correctement sécurisée.

Sécuriser l’application serveur IPC sur les appelants inattendus

L’application serveur IPC héberge les services RPC pour que d’autres applications appellent. Les appelants entrants doivent être authentifiés pour empêcher les clients non approuvés d’effectuer des appels RPC au serveur.

La sécurité du transport est une option pour sécuriser un serveur. Les transports IPC, comme les sockets de domaine Unix et les canaux nommés, prennent en charge la limitation de l’accès en fonction des autorisations du système d’exploitation :

  • Les canaux nommés prennent en charge la sécurisation d’un canal avec le modèle de contrôle d’accès Windows. Les droits d’accès peuvent être configurés dans .NET lorsqu’un serveur démarre à l’aide de la classe PipeSecurity.
  • Les sockets de domaine Unix prennent en charge la sécurisation d’un socket avec des autorisations de fichier.

Une autre option pour sécuriser un serveur IPC consiste à utiliser l’authentification et l’autorisation intégrées à ASP.NET Core. Par exemple, le serveur peut être configuré pour exiger authentification par certificat. Les appels RPC effectués par les applications clientes sans le certificat requis échouent avec une réponse non autorisée.

Valider le serveur dans l’application cliente IPC

Il est important que l’application cliente valide l’identité du serveur qu’elle appelle. La validation est nécessaire pour protéger un acteur malveillant contre l’arrêt du serveur approuvé, l’exécution de ses propres données et l’acceptation des données entrantes à partir de clients.

Les canaux nommés prennent en charge l’obtention du compte sous lequel un serveur s’exécute. Un client peut valider que le serveur a été lancé par le compte attendu :

internal static bool CheckPipeConnectionOwnership(
    NamedPipeClientStream pipeStream, SecurityIdentifier expectedOwner)
{
    var remotePipeSecurity = pipeStream.GetAccessControl();
    var remoteOwner = remotePipeSecurity.GetOwner(typeof(SecurityIdentifier));
    return expectedOwner.Equals(remoteOwner);
}

Une autre option pour valider le serveur consiste à sécuriser ses points de terminaison avec https à l’intérieur ASP.NET Core. Le client peut configurer SocketsHttpHandler pour valider que le serveur utilise le certificat attendu lorsque la connexion est établie.

var socketsHttpHandler = new SocketsHttpHandler()
{
    SslOptions = new SslOptions()
    {
        RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) =>
        {
            if (sslPolicyErrors != SslPolicyErrors.None)
            {
                return false;
            }

            // Validate server cert thumbprint matches the expected thumbprint.
        }
    }
};

Protéger contre l’escalade des privilèges de canal nommé

Les canaux nommés prennent en charge une fonctionnalité appelée emprunt d’identité. À l’aide de l’emprunt d’identité, le serveur de canaux nommés peut exécuter du code avec les privilèges de l’utilisateur client. Il s’agit d’une fonctionnalité puissante, mais peut permettre à un serveur à privilèges faibles d’emprunter l’identité d’un appelant à privilèges élevés, puis d’exécuter du code malveillant.

Le client peut se protéger contre cette attaque en n’autorisant pas l’emprunt d’identité lors de la connexion à un serveur. Sauf si nécessaire pour un serveur, une valeur TokenImpersonationLevel de None ou de Anonymous doit être utilisée lors de la création d’une connexion cliente :

using var pipeClient = new NamedPipeClientStream(
    serverName: ".", pipeName: "testpipe", PipeDirection.In, PipeOptions.None, TokenImpersonationLevel.None);
await pipeClient.ConnectAsync();

TokenImpersonationLevel.None est la valeur par défaut dans les constructeurs NamedPipeClientStream qui n’ont pas de paramètre impersonationLevel.

Configurer le client et le serveur

Le client et le serveur doivent être configurés pour utiliser un transport de communication entre processus (IPC). Pour plus d’informations sur la configuration de Kestrel et SocketsHttpHandler de l’utilisation d’IPC :

Notes

La prise en charge intégrée des canaux nommés dans ASP.NET Core nécessite .NET 8 ou version ultérieure.

Les processus exécutés sur le même ordinateur peuvent être conçus pour communiquer entre eux. Les systèmes d’exploitation fournissent des technologies permettant une communication entre processus (IPC) rapide et efficace. Les sockets de domaine Unix et les canaux nommés sont des exemples populaires de technologies IPC.

.NET prend en charge la communication entre processus à l’aide de gRPC.

Remarque

La prise en charge intégrée des canaux nommés dans ASP.NET Core nécessite .NET 8 ou version ultérieure.
Pour plus d’informations, consultez la version .NET 8 ou ultérieure de cette rubrique

Prise en main

Les appels gRPC sont envoyés depuis un client à un serveur. Pour communiquer entre les applications sur un ordinateur avec gRPC, au moins une application doit héberger un serveur gRPC ASP.NET Core.

ASP.NET Core et gRPC peuvent être hébergés dans n’importe quelle application utilisant .NET Core 3.1 ou ultérieur en ajoutant le framework Microsoft.AspNetCore.App au projet.

<Project Sdk="Microsoft.NET.Sdk">

  <ItemGroup>
    <FrameworkReference Include="Microsoft.AspNetCore.App" />
    <PackageReference Include="Grpc.AspNetCore" Version="2.47.0" />
    <Protobuf Include="Protos\greet.proto" GrpcServices="Server" />
  </ItemGroup>

</Project>

Le fichier projet précédent :

  • Ajoute une référence de framework à Microsoft.AspNetCore.App. La référence du framework permet aux applications non-ASP.NET Core, comme les services Windows, les applications WPF ou les applications WinForms, d’utiliser ASP.NET Core et d’héberger un serveur ASP.NET Core.
  • Ajoute une référence de package NuGet à Grpc.AspNetCore.
  • Ajoute un fichier .proto.

Configurer des sockets de domaine Unix

Les appels gRPC entre un client et un serveur sur différents ordinateurs sont généralement envoyés via des sockets TCP. TCP a été conçu pour communiquer sur un réseau. Les sockets de domaine Unix (UDS) constituent une technologie IPC largement prise en charge, qui est plus efficace que TCP quand le client et le serveur sont sur la même machine. .NET fournit une prise en charge intégrée d’UDS dans les applications clientes et serveurs.

Conditions requises :

Configurer le serveur

Les sockets de domaine Unix sont pris en charge par Kestrel, qui est configuré dans Program.cs :

public static readonly string SocketPath = Path.Combine(Path.GetTempPath(), "socket.tmp");

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.UseStartup<Startup>();
            webBuilder.ConfigureKestrel(options =>
            {
                if (File.Exists(SocketPath))
                {
                    File.Delete(SocketPath);
                }
                options.ListenUnixSocket(SocketPath, listenOptions =>
                {
                    listenOptions.Protocols = HttpProtocols.Http2;
                });
            });
        });

L’exemple précédent :

  • Configure les points de terminaison de Kestrel dans ConfigureKestrel.
  • Appelle ListenUnixSocket pour écouter un UDS avec le chemin spécifié.
  • Crée un point de terminaison d’UDS qui n’est pas configuré pour utiliser HTTPS. Pour plus d’informations sur l’activation du protocole HTTPS, consultez Kestrel Configuration du point de terminaison HTTPS.

Configuration de client

GrpcChannel prend en charge les appels gRPC sur des transports personnalisés. Lorsqu’un canal est créé, il peut être configuré avec un SocketsHttpHandler qui a un ConnectCallback personnalisé. Le rappel permet au client d’établir des connexions via des transports personnalisés, puis d’envoyer des requêtes HTTP sur ce transport.

Exemple de fabrique de connexions de sockets de domaine Unix :

public class UnixDomainSocketConnectionFactory
{
    private readonly EndPoint _endPoint;

    public UnixDomainSocketConnectionFactory(EndPoint endPoint)
    {
        _endPoint = endPoint;
    }

    public async ValueTask<Stream> ConnectAsync(SocketsHttpConnectionContext _,
        CancellationToken cancellationToken = default)
    {
        var socket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified);

        try
        {
            await socket.ConnectAsync(_endPoint, cancellationToken).ConfigureAwait(false);
            return new NetworkStream(socket, true);
        }
        catch
        {
            socket.Dispose();
            throw;
        }
    }
}

Utilisation de la fabrique de connexion personnalisée pour créer un canal :

public static readonly string SocketPath = Path.Combine(Path.GetTempPath(), "socket.tmp");

public static GrpcChannel CreateChannel()
{
    var udsEndPoint = new UnixDomainSocketEndPoint(SocketPath);
    var connectionFactory = new UnixDomainSocketConnectionFactory(udsEndPoint);
    var socketsHttpHandler = new SocketsHttpHandler
    {
        ConnectCallback = connectionFactory.ConnectAsync
    };

    return GrpcChannel.ForAddress("http://localhost", new GrpcChannelOptions
    {
        HttpHandler = socketsHttpHandler
    });
}

Les canaux créés à l’aide du code précédent envoient des appels gRPC sur des sockets de domaine Unix. La prise en charge d’autres technologies IPC peut être implémentée en utilisant l’extensibilité dans Kestrel et SocketsHttpHandler.