Kommunikation mellan processer med gRPC

Anmärkning

Det här är inte den senaste versionen av den här artikeln. Den aktuella versionen finns i .NET 10-versionen av den här artikeln.

Varning

Den här versionen av ASP.NET Core stöds inte längre. Mer information finns i supportpolicyn för .NET och .NET Core. För den nuvarande utgåvan, se .NET 9-versionen av den här artikeln .

Processer som körs på samma dator kan utformas för att kommunicera med varandra. Operativsystem tillhandahåller tekniker för att möjliggöra snabb och effektiv kommunikation mellan processer (IPC). Populära exempel på IPC-tekniker är Unix-domänsocketer och Namngivna rör.

.NET har stöd för kommunikation mellan processer med hjälp av gRPC.

Inbyggt stöd för namngivna rör i ASP.NET Core kräver .NET 8 eller senare.

Get started

IPC-anrop skickas från en klient till en server. Om du vill kommunicera mellan appar på en dator med gRPC måste minst en app vara värd för en ASP.NET Core gRPC-server.

En ASP.NET Core gRPC-server skapas vanligtvis från gRPC-mallen. Projektfilen som skapas av mallen använder Microsoft.NET.SDK.Web som 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>

Värdet Microsoft.NET.SDK.Web SDK lägger automatiskt till en referens till ASP.NET Core-ramverket. Referensen gör att appen kan använda ASP.NET Core-typer som krävs för att vara värd för en server.

Det går också att lägga till en server i befintliga non-ASP.NET Core-projekt, till exempel Windows Services, WPF-appar eller WinForms-appar. Mer information finns i Värda gRPC i projekt som inte är ASP.NET Core.

Kommunikation mellan processer (IPC) transporter

gRPC-anrop mellan en klient och server på olika datorer skickas vanligtvis via TCP-socketar. TCP är ett bra val för kommunikation mellan ett nätverk eller Internet. IPC-transporter ger dock fördelar vid kommunikation mellan processer på samma dator:

  • Mindre omkostnader och snabbare överföringshastigheter.
  • Integrering med os-säkerhetsfunktioner.
  • Använder inte TCP-portar, som är en begränsad resurs.

.NET stöder flera IPC-transporter:

Beroende på operativsystemet kan plattformsoberoende appar välja olika IPC-transporter. En app kan kontrollera operativsystemet vid start och välja önskad transport för plattformen:

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

Säkerhetsfrågor

IPC-appar skickar och tar emot RPC-samtal. Extern kommunikation är en potentiell attackvektor för IPC-appar och måste skyddas korrekt.

Skydda IPC-serverappen mot oväntade anropare

IPC-serverappen är värd för RPC-tjänster som andra appar kan anropa. Inkommande anropare bör autentiseras för att förhindra att ej betrodda klienter gör RPC-anrop till servern.

Transportsäkerhet är ett alternativ för att skydda en server. IPC-transporter, till exempel Unix-domänsocketer och namngivna rör, stöder begränsning av åtkomst baserat på operativsystembehörigheter:

  • Namngivna kanaler stöder skydd av en kanal med Windows-åtkomstkontrollmodellen. Åtkomsträttigheter kan konfigureras i .NET när en server startas med hjälp av PipeSecurity klassen .
  • Unix-domänsocketer stöder skydd av en socket med filbehörigheter.

Ett annat alternativ för att skydda en IPC-server är att använda autentisering och auktorisering som är inbyggd i ASP.NET Core. Servern kan till exempel konfigureras för att kräva certifikatautentisering. RPC-anrop som görs av klientappar utan det nödvändiga certifikatet misslyckas med ett obehörigt svar.

Verifiera servern i IPC-klientappen

Det är viktigt att klientappen verifierar identiteten på den server som anropas. Validering är nödvändigt för att skydda mot en skadlig aktör från att stoppa den betrodda servern, köra sina egna och acceptera inkommande data från klienter.

Namngivna rör ger stöd för att identifiera det konto som en server körs under. En klient kan verifiera att servern startades av det förväntade kontot:

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

Ett annat alternativ för att verifiera servern är att skydda sina slutpunkter med HTTPS i ASP.NET Core. Klienten kan konfigurera SocketsHttpHandler för att verifiera att servern använder det förväntade certifikatet när anslutningen upprättas.

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

Skydda mot eskalering av namngivna rörprivilegier

Namngivna rör stöder en funktion som kallas impersonation. Med hjälp av personifiering kan den namngivna pipes-servern köra kod med klientanvändarens behörighet. Det här är en kraftfull funktion men kan göra det möjligt för en server med låg behörighet att personifiera en anropare med hög behörighet och sedan köra skadlig kod.

Kunder kan skydda mot den här attacken genom att inte tillåta identitetsförfalskning vid anslutning till en server. Om det inte krävs av en server ska ett TokenImpersonationLevel värde på None eller Anonymous användas när du skapar en klientanslutning:

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

TokenImpersonationLevel.None är standardvärdet i NamedPipeClientStream konstruktorer som inte har någon impersonationLevel parameter.

Konfigurera klient och server

Klienten och servern måste konfigureras för att använda en IPC-transport (inter-process communication). Mer information om hur du konfigurerar Kestrel och SocketsHttpHandler för att använda IPC:

Anmärkning

Inbyggt stöd för namngivna rör i ASP.NET Core kräver .NET 8 eller senare.

Processer som körs på samma dator kan utformas för att kommunicera med varandra. Operativsystem tillhandahåller tekniker för att möjliggöra snabb och effektiv kommunikation mellan processer (IPC). Populära exempel på IPC-tekniker är Unix-domänsocketer och Namngivna rör.

.NET har stöd för kommunikation mellan processer med hjälp av gRPC.

Anmärkning

Inbyggt stöd för namngivna rör i ASP.NET Core kräver .NET 8 eller senare.
Mer information finns i .NET 8 eller senare versionen av det här avsnittet

Get started

gRPC-anrop skickas från en klient till en server. Om du vill kommunicera mellan appar på en dator med gRPC måste minst en app vara värd för en ASP.NET Core gRPC-server.

ASP.NET Core och gRPC kan finnas i valfri app med hjälp av .NET Core 3.1 eller senare genom att lägga till ramverket Microsoft.AspNetCore.App i projektet.

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

Föregående projektfil:

  • Lägger till en ramverksreferens till Microsoft.AspNetCore.App. Med ramverksreferensen kan non-ASP.NET Core-appar, till exempel Windows Services, WPF-appar eller WinForms-appar, använda ASP.NET Core och vara värd för en ASP.NET Core-server.
  • Lägger till en NuGet-paketreferens till Grpc.AspNetCore.
  • Lägger till en .proto fil.

Konfigurera Unix-domänsocketer

gRPC-anrop mellan en klient och server på olika datorer skickas vanligtvis via TCP-socketar. TCP har utformats för kommunikation i ett nätverk. Unix-domänuttag (UDS) är en IPC-teknik som stöds i stor utsträckning och som är effektivare än TCP när klienten och servern finns på samma dator. .NET tillhandahåller inbyggt stöd för UDS i klient- och serverappar.

Krav:

Serverkonfiguration

Unix-domänsocketer (UDS) stöds av Kestrel, som konfigureras i 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;
                });
            });
        });

Föregående exempel:

  • Konfigurerar Kestrelslutpunkter i ConfigureKestrel.
  • Anrop ListenUnixSocket för att lyssna på en UDS med den angivna sökvägen.
  • Skapar en UDS-slutpunkt som inte är konfigurerad att använda HTTPS. Information om hur du aktiverar HTTPS finns i Kestrel HTTPS-slutpunktskonfiguration.

Klientkonfiguration

GrpcChannel stöder gRPC-anrop via anpassade transporter. När en kanal skapas kan den konfigureras med en SocketsHttpHandler som har en anpassad ConnectCallback. Återanropet gör att klienten kan upprätta anslutningar via anpassade transporter och sedan skicka HTTP-begäranden via den transporten.

Exempel på anslutningsfabrik för Unix-domänsocketer:

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

Använd den anpassade anslutningsfabriken för att skapa en kanal:

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

Kanaler som skapats med hjälp av föregående kod skickar gRPC-anrop via Unix-domänsocketer. Stöd för andra IPC-tekniker kan implementeras med utökningsbarheten i Kestrel och SocketsHttpHandler.