Comunicazione tra processi con gRPC

Nota

Questa non è la versione più recente di questo articolo. Per la versione corrente, vedere la versione .NET 8 di questo articolo.

Avviso

Questa versione di ASP.NET Core non è più supportata. Per altre informazioni, vedere Criteri di supporto di .NET e .NET Core. Per la versione corrente, vedere la versione .NET 8 di questo articolo.

Importante

Queste informazioni si riferiscono a un prodotto non definitive che può essere modificato in modo sostanziale prima che venga rilasciato commercialmente. Microsoft non riconosce alcuna garanzia, espressa o implicita, in merito alle informazioni qui fornite.

Per la versione corrente, vedere la versione .NET 8 di questo articolo.

I processi in esecuzione nello stesso computer possono essere progettati per comunicare tra loro. I sistemi operativi forniscono tecnologie per abilitare la comunicazione tra processi (IPC) veloce ed efficiente. Esempi comuni di tecnologie IPC sono socket di dominio Unix e named pipe.

.NET offre supporto per la comunicazione tra processi tramite gRPC.

Il supporto predefinito per named pipe in ASP.NET Core richiede .NET 8 o versione successiva.

Operazioni preliminari

Le chiamate IPC vengono inviate da un client a un server. Per comunicare tra app in un computer con gRPC, almeno un'app deve ospitare un server ASP.NET Core gRPC.

Un server ASP.NET Core gRPC viene in genere creato dal modello gRPC. Il file di progetto creato dal modello usa Microsoft.NET.SDK.Web come 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>

Il valore dell'SDK Microsoft.NET.SDK.Web aggiunge automaticamente un riferimento al framework ASP.NET Core. Il riferimento consente all'app di usare ASP.NET tipi core necessari per ospitare un server.

È anche possibile aggiungere un server a progetti non-ASP.NET Core esistenti, ad esempio Servizi Windows, app WPF o app WinForms. Per altre informazioni, vedere Host gRPC nei progetti non-ASP.NET Core.

Trasporti IPC (Inter-Process Communication)

Le chiamate gRPC tra un client e un server in computer diversi vengono in genere inviate tramite socket TCP. TCP è una buona scelta per la comunicazione tra una rete o Internet. Tuttavia, i trasporti IPC offrono vantaggi durante la comunicazione tra processi nella stessa macchina:

  • Minore sovraccarico e velocità di trasferimento più veloci.
  • Integrazione con le funzionalità di sicurezza del sistema operativo.
  • Non usa porte TCP, che sono una risorsa limitata.

.NET supporta più trasporti IPC:

A seconda del sistema operativo, le app multipiattaforma possono scegliere trasporti IPC diversi. Un'app può controllare il sistema operativo all'avvio e scegliere il trasporto desiderato per tale piattaforma:

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

Considerazioni relative alla sicurezza

Le app IPC inviano e ricevono chiamate RPC. La comunicazione esterna è un potenziale vettore di attacco per le app IPC e deve essere protetta correttamente.

Proteggere l'app server IPC da chiamanti imprevisti

L'app server IPC ospita servizi RPC per le altre app da chiamare. I chiamanti in ingresso devono essere autenticati per impedire ai client non attendibili di effettuare chiamate RPC al server.

La sicurezza del trasporto è un'opzione per proteggere un server. I TRASPORTI IPC, ad esempio socket di dominio Unix e named pipe, supportano la limitazione dell'accesso in base alle autorizzazioni del sistema operativo:

  • Le named pipe supportano la protezione di una pipe con il modello di controllo di accesso di Windows. I diritti di accesso possono essere configurati in .NET quando un server viene avviato usando la PipeSecurity classe .
  • I socket di dominio Unix supportano la protezione di un socket con autorizzazioni per i file.

Un'altra opzione per proteggere un server IPC consiste nell'usare l'autenticazione e l'autorizzazione integrate in ASP.NET Core. Ad esempio, il server può essere configurato per richiedere l'autenticazione del certificato. Le chiamate RPC effettuate dalle app client senza il certificato richiesto hanno esito negativo con una risposta non autorizzata.

Convalidare il server nell'app client IPC

È importante che l'app client convalide l'oggetto identity del server che sta chiamando. La convalida è necessaria per proteggersi da un attore malintenzionato dall'arresto del server attendibile, dall'esecuzione e dall'accettazione dei dati in ingresso dai client.

Le named pipe forniscono il supporto per ottenere l'account in cui è in esecuzione un server. Un client può convalidare che il server sia stato avviato dall'account previsto:

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

Un'altra opzione per convalidare il server consiste nel proteggere gli endpoint con HTTPS all'interno di ASP.NET Core. Il client può configurare per convalidare SocketsHttpHandler che il server usi il certificato previsto quando viene stabilita la connessione.

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

Protezione dall'escalation dei privilegi named pipe

Named pipe supporta una funzionalità denominata rappresentazione. Usando la rappresentazione, il server named pipe può eseguire il codice con i privilegi dell'utente client. Si tratta di una funzionalità potente, ma può consentire a un server con privilegi limitati di rappresentare un chiamante con privilegi elevati e quindi eseguire codice dannoso.

Il client può proteggersi da questo attacco non consentendo la rappresentazione durante la connessione a un server. A meno che non sia richiesto da un server, è necessario usare un TokenImpersonationLevel valore di o durante Anonymous la creazione di None una connessione client:

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

TokenImpersonationLevel.None è il valore predefinito nei NamedPipeClientStream costruttori che non hanno un impersonationLevel parametro.

Configurare client e server

Il client e il server devono essere configurati per l'uso di un trasporto IPC (Inter-Process Communication). Per altre informazioni sulla configurazione Kestrel e SocketsHttpHandler sull'uso di IPC:

Nota

Il supporto predefinito per named pipe in ASP.NET Core richiede .NET 8 o versione successiva.

I processi in esecuzione nello stesso computer possono essere progettati per comunicare tra loro. I sistemi operativi forniscono tecnologie per abilitare la comunicazione tra processi (IPC) veloce ed efficiente. Esempi comuni di tecnologie IPC sono socket di dominio Unix e named pipe.

.NET offre supporto per la comunicazione tra processi tramite gRPC.

Nota

Il supporto predefinito per named pipe in ASP.NET Core richiede .NET 8 o versione successiva.
Per altre informazioni, vedere .NET 8 o versione successiva di questo argomento

Operazioni preliminari

Le chiamate gRPC vengono inviate da un client a un server. Per comunicare tra app in un computer con gRPC, almeno un'app deve ospitare un server ASP.NET Core gRPC.

ASP.NET Core e gRPC possono essere ospitati in qualsiasi app usando .NET Core 3.1 o versione successiva aggiungendo il Microsoft.AspNetCore.App framework al progetto.

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

Il file di progetto precedente:

  • Aggiunge un riferimento al framework a Microsoft.AspNetCore.App. Il riferimento al framework consente non-ASP.NET app Core, ad esempio Servizi Windows, app WPF o app WinForms per usare ASP.NET Core e ospitare un server ASP.NET Core.
  • Aggiunge un riferimento al pacchetto NuGet a Grpc.AspNetCore.
  • Aggiunge un .proto file.

Configurare i socket di dominio Unix

Le chiamate gRPC tra un client e un server in computer diversi vengono in genere inviate tramite socket TCP. TCP è stato progettato per la comunicazione tra una rete. I socket di dominio Unix (UDS) sono una tecnologia IPC ampiamente supportata che è più efficiente di TCP quando il client e il server si trovano nello stesso computer. .NET offre il supporto predefinito per il servizio definito dall'utente nelle app client e server.

Requisiti:

Configurazione del server

I socket di dominio Unix (UDS) sono supportati da Kestrel, che è configurato in 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'esempio precedente:

  • Configura gli Kestrelendpoint in ConfigureKestrel.
  • Chiama ListenUnixSocket per restare in ascolto di un tipo definito dall'utente con il percorso specificato.
  • Crea un endpoint definito dall'utente che non è configurato per l'uso di HTTPS. Per informazioni sull'abilitazione di HTTPS, vedere Kestrel Configurazione dell'endpoint HTTPS.

Configurazione del client

GrpcChannel supporta l'esecuzione di chiamate gRPC su trasporti personalizzati. Quando viene creato un canale, può essere configurato con un SocketsHttpHandler oggetto con un oggetto personalizzato ConnectCallback. Il callback consente al client di effettuare connessioni su trasporti personalizzati e quindi inviare richieste HTTP su tale trasporto.

Esempio di factory di connessione socket di dominio 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;
        }
    }
}

Uso della factory di connessione personalizzata per creare un canale:

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

Canali creati usando il codice precedente inviano chiamate gRPC su socket di dominio Unix. Il supporto per altre tecnologie IPC può essere implementato usando l'estendibilità in Kestrel e SocketsHttpHandler.