Межпроцессное взаимодействие с помощью gRPC

Процессы, выполняемые на одном компьютере, могут быть разработаны для взаимодействия друг с другом. Операционные системы предоставляют технологии для быстрого и эффективного внутрипроцессного взаимодействия (IPC). Популярными примерами технологий IPC являются сокеты домена Unix и именованные каналы.

.NET обеспечивает поддержку внутрипроцессного взаимодействия с помощью gRPC.

Встроенная поддержка именованных каналов в ASP.NET Core требует .NET 8 или более поздней версии.

Начать

Вызовы IPC отправляются с клиента на сервер. Чтобы обеспечить обмен данными между приложениями на компьютере с gRPC, по крайней мере одно приложение должно размещать сервер gRPC ASP.NET Core.

Сервер ASP.NET Core gRPC обычно создается из шаблона gRPC. Файл проекта, созданный шаблоном, используется Microsoft.NET.SDK.Web в качестве пакета 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>

Значение Microsoft.NET.SDK.Web пакета SDK автоматически добавляет ссылку на платформу ASP.NET Core. Ссылка позволяет приложению использовать ASP.NET основные типы, необходимые для размещения сервера.

Кроме того, можно добавить сервер в существующие проекты non-ASP.NET Core, такие как службы Windows, приложения WPF или приложения WinForms. Дополнительные сведения см. в разделе "Узел gRPC" в non-ASP.NET основных проектах .

Транспорты обмена данными между процессами (IPC)

Вызовы gRPC между клиентом и сервером на разных компьютерах обычно отправляются через сокеты TCP. TCP — это хороший выбор для обмена данными между сетью или Интернетом. Однако транспортЫ IPC предлагают преимущества при обмене данными между процессами на одном компьютере:

  • Меньше накладных расходов и скоростей передачи.
  • Интеграция с функциями безопасности ОС.
  • Не использует tcp-порты, которые являются ограниченным ресурсом.

.NET поддерживает несколько транспортных объектов IPC:

В зависимости от ОС кроссплатформенные приложения могут выбирать разные транспорты IPC. Приложение может проверка ОС при запуске и выбрать нужный транспорт для этой платформы:

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

Вопросы безопасности

Приложения IPC отправляют и получают вызовы RPC. Внешнее взаимодействие — это потенциальный вектор атаки для приложений IPC и должен быть правильно защищен.

Безопасное приложение сервера IPC для непредвиденных вызывающих объектов

Серверное приложение IPC размещает службы RPC для вызова других приложений. Входящие вызывающие серверы должны проходить проверку подлинности, чтобы предотвратить ненадежные клиенты от вызова RPC к серверу.

Безопасность транспорта — это один из вариантов защиты сервера. ТранспортЫ IPC, такие как сокеты домена Unix и именованные каналы, поддерживают ограничение доступа на основе разрешений операционной системы:

  • Именованные каналы поддерживают защиту канала с помощью модели управления доступом Windows. Права доступа можно настроить в .NET при запуске сервера с помощью PipeSecurity класса.
  • Сокеты домена Unix поддерживают защиту сокета с разрешениями файла.

Еще одним вариантом защиты сервера IPC является использование проверки подлинности и авторизации, встроенной в ASP.NET Core. Например, сервер можно настроить для проверки подлинности сертификата. Вызовы RPC, выполненные клиентскими приложениями без обязательного сертификата, завершаются ошибкой с несанкционированным ответом.

Проверка сервера в клиентском приложении IPC

Для клиентского приложения важно проверить удостоверение вызываемого сервера. Проверка необходима для защиты от вредоносных субъектов от остановки доверенного сервера, запуска собственного и приема входящих данных от клиентов.

Именованные каналы обеспечивают поддержку получения учетной записи, в которую выполняется сервер. Клиент может проверить, был запущен ли сервер ожидаемой учетной записью:

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

Еще одним вариантом проверки сервера является защита конечных точек с помощью HTTPS внутри ASP.NET Core. Клиент может настроить SocketsHttpHandler для проверки, что сервер использует ожидаемый сертификат при установке подключения.

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

Защита от эскалации привилегий именованного канала

Именованные каналы поддерживают функцию, называемую олицетворением. С помощью олицетворения сервер именованных каналов может выполнять код с привилегиями пользователя клиента. Это мощная функция, но может позволить серверу с низким уровнем привилегий олицетворить вызывающий объект с высоким уровнем привилегий, а затем запустить вредоносный код.

Клиент может защититься от этой атаки, не разрешая олицетворение при подключении к серверу. Если сервер не требуется, TokenImpersonationLevel при создании клиентского подключения следует использовать значение None или Anonymous его значение:

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

TokenImpersonationLevel.None — значение по умолчанию в NamedPipeClientStream конструкторах, у которых нет impersonationLevel параметра.

Настройка клиента и сервера

Клиент и сервер должны быть настроены для использования транспорта обмена данными между процессами (IPC). Дополнительные сведения о настройке Kestrel и SocketsHttpHandler использовании IPC:

Примечание.

Встроенная поддержка именованных каналов в ASP.NET Core требует .NET 8 или более поздней версии.

Процессы, выполняемые на одном компьютере, могут быть разработаны для взаимодействия друг с другом. Операционные системы предоставляют технологии для быстрого и эффективного внутрипроцессного взаимодействия (IPC). Популярными примерами технологий IPC являются сокеты домена Unix и именованные каналы.

.NET обеспечивает поддержку внутрипроцессного взаимодействия с помощью gRPC.

Примечание.

Встроенная поддержка именованных каналов в ASP.NET Core требует .NET 8 или более поздней версии.
Дополнительные сведения см . в версии .NET 8 или более поздней версии этого раздела.

Начать

Вызовы gRPC отправляются с клиента на сервер. Чтобы обеспечить обмен данными между приложениями на компьютере с gRPC, по крайней мере одно приложение должно размещать сервер gRPC ASP.NET Core.

ASP.NET Core и gRPC можно разместить в любом приложении с помощью .NET Core 3.1 или более поздней версии. Для этого необходимо добавить платформу Microsoft.AspNetCore.App в проект.

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

В предыдущем файле проекта выполняются следующие действия:

  • Добавляется ссылка на платформу в Microsoft.AspNetCore.App. Ссылка на платформу позволяет приложениям, отличным от ASP.NET Core, таким как службы Windows, приложения WPF или приложения WinForms, использовать ASP.NET Core и размещать сервер ASP.NET Core.
  • Добавляется ссылка на пакет NuGet в Grpc.AspNetCore.
  • Добавляется файл .proto.

Настройка сокетов домена Unix

Вызовы gRPC между клиентом и сервером на разных компьютерах обычно отправляются через сокеты TCP. Протокол TCP был разработан для обмена данными по сети. Сокеты домена Unix (UDS) — это широко поддерживаемая технология IPC, которая эффективнее чем TCP, когда клиент и сервер находятся на одном компьютере. .NET обеспечивает встроенную поддержку UDS в клиентских и серверных приложениях.

Требования:

Конфигурация сервера

Сокеты домена Unix (UDS) поддерживаются Kestrel, который настраивается в 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;
                });
            });
        });

Предшествующий пример:

  • Настраивает конечные точки Kestrel в ConfigureKestrel.
  • Вызывает ListenUnixSocket для прослушивания UDS по указанному пути.
  • Создает конечную точку UDS, которая не настроена для использования HTTPS. Дополнительные сведения о включении HTTPS см. в разделе о настройке конечной точки HTTPS Kestrel.

Настройка клиента

GrpcChannel поддерживает выполнение вызовов gRPC через настраиваемый транспорт. При создании канала его можно настроить с помощью SocketsHttpHandler, у которого имеется настраиваемый ConnectCallback. Обратный вызов позволяет клиенту устанавливать соединения через настраиваемые транспорты, а затем передавать HTTP-запросы через этот транспорт.

Пример фабрики подключений для сокетов доменов 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;
        }
    }
}

Использование настраиваемой фабрики подключений для создания канала:

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

Каналы, созданные с помощью приведенного выше кода, отправляют вызовы gRPC через сокеты доменов UNIX. Поддержку других технологий IPC можно реализовать, используя возможности расширяемости в Kestrel и SocketsHttpHandler.