gRPC を使用したプロセス間通信

同じマシン上で実行されているプロセスは、相互に通信するように設計できます。 オペレーティング システムは、高速かつ効率的なプロセス間通信 (IPC) を可能にするテクノロジを提供します。 IPC テクノロジの一般的な例として、Unix ドメイン ソケットと名前付きパイプがあります。

.NET では、gRPC を使用したプロセス間通信のサポートが提供されます。

ASP.NET Core での名前付きパイプの組み込みサポートには、.NET 8 以降が必要です。

はじめに

IPC 呼び出しは、クライアントからサーバーに送信されます。 gRPC を使用してマシン上のアプリ間で通信するには、少なくとも 1 つのアプリで ASP.NET Core gRPC サーバーをホストする必要があります。

通常、ASP.NET Core gRPC サーバーは、gRPC テンプレートから作成されます。 テンプレートによって作成されたプロジェクト ファイルでは、SDK として Microsoft.NET.SDK.Web が使用されます。

<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 Core の型を使用できます。

Windows サービス、WPF アプリ、WinForms アプリなど、既存の非 ASP.NET Core プロジェクトにサーバーを追加することもできます。 詳細については、非 ASP.NET Core プロジェクトでの gRPC のホストに関する記事を参照してください。

プロセス間通信 (IPC) のトランスポート

異なるマシン上のクライアントとサービス間の gRPC 呼び出しは、通常、TCP ソケットを介して送信されます。 TCP は、ネットワークまたはインターネット経由で通信する場合に適しています。 ただし、IPC トランスポートにより、同じコンピューター上のプロセス間で通信する場合に次の利点が提供されます。

  • 少ないオーバーヘッドとより速い転送速度。
  • OS セキュリティ機能との統合。
  • 制限付きリソースである TCP ポートは使用しません。

.NET では、複数の IPC トランスポートがサポートされています。

OS によっては、クロスプラットフォーム アプリで異なる IPC トランスポートが選択される場合があります。 アプリで、起動時に OS をチェックして、そのプラットフォームに望ましいトランスポートを選択できます。

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 呼び出しを行うことを防ぐために、着信する呼び出し元を認証する必要があります。

トランスポート セキュリティは、サーバーをセキュリティで保護するための 1 つのオプションです。 Unix ドメイン ソケットや名前付きパイプなどの IPC トランスポートでは、オペレーティング システムのアクセス許可に基づくアクセスの制限がサポートされます。

  • 名前付きパイプは、Windows アクセス制御モデルを使用したパイプのセキュリティ保護をサポートしています。 PipeSecurity クラスを使用してサーバーを起動するときは、.NET でアクセス権を構成できます。
  • Unix ドメイン ソケットは、ファイルのアクセス許可を使用してソケットのセキュリティ保護をサポートしています。

IPC サーバーをセキュリティで保護するためのもう 1 つのオプションは、ASP.NET Core に組み込まれている認証と承認を使用することです。 たとえば、証明書認証を要求するようにサーバーを構成できます。 必要な証明書がないクライアント アプリによって行われた RPC 呼び出しは、権限がないことを示す応答で失敗します。

IPC クライアント アプリでサーバーを検証する

クライアント アプリで、呼び出しているサーバーの ID を検証することが重要です。 検証は、悪意のあるアクターが信頼されたサーバーを停止し、独自のサーバーを実行し、クライアントからの着信データを受け入れることから保護するために必要です。

名前付きパイプは、サーバーが実行されているアカウントを取得するためのサポートを提供します。 クライアントは、予期されるアカウントによってサーバーが起動されたことを検証できます。

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

サーバーを検証するためのもう 1 つのオプションは、ASP.NET Core 内の HTTPS を使用してエンドポイントをセキュリティで保護することです。 クライアントは、接続が確立されたときに、予期される証明書をサーバーが使用していることを検証するように 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.
        }
    }
};

名前付きパイプの特権エスカレーションから保護する

名前付きパイプは、偽装と呼ばれる機能をサポートしています。 偽装を使用すると、名前付きパイプ サーバーは、クライアント ユーザーの特権でコードを実行できます。 これは強力な機能ですが、低い特権のサーバーで高い特権の呼び出し元を偽装して、悪意のあるコードを実行することを許可する可能性があります。

クライアントは、サーバーに接続するときに偽装を許可しないことで、この攻撃から保護できます。 サーバーで必要な場合を除き、クライアント接続の作成時は None または AnonymousTokenImpersonationLevel 値を使用する必要があります。

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

TokenImpersonationLevel.None が、NamedPipeClientStream コンストラクターの既定値であり、impersonationLevel パラメーターはありません。

クライアントとサーバーを構成する

プロセス間通信 (IPC) のトランスポートを使うようにクライアントとサーバーを構成する必要があります。 IPC を使うように Kestrel と SocketsHttpHandler を構成する方法について詳しくは、次を参照してください。

Note

ASP.NET Core での名前付きパイプの組み込みサポートには、.NET 8 以降が必要です。

同じマシン上で実行されているプロセスは、相互に通信するように設計できます。 オペレーティング システムは、高速かつ効率的なプロセス間通信 (IPC) を可能にするテクノロジを提供します。 IPC テクノロジの一般的な例として、Unix ドメイン ソケットと名前付きパイプがあります。

.NET では、gRPC を使用したプロセス間通信のサポートが提供されます。

Note

ASP.NET Core での名前付きパイプの組み込みサポートには、.NET 8 以降が必要です。
詳細については、このトピックの .NET 8 以降のバージョンを参照してください

はじめに

gRPC 呼び出しは、クライアントからサーバーに送信されます。 gRPC を使用してマシン上のアプリ間で通信するには、少なくとも 1 つのアプリで ASP.NET Core gRPC サーバーをホストする必要があります。

ASP.NET Core と gRPC は、プロジェクトに Microsoft.AspNetCore.App フレームワークを追加することで、.NET Core 3.1 以降を使用して任意のアプリでホストできます。

<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 にフレームワーク参照を追加します。 フレームワーク参照を使用すると、Windows Services、WPF アプリ、WinForms アプリなどの ASP.NET Core 以外のアプリで、ASP.NET Core を使用し、ASP.NET Core サーバーをホストできます。
  • NuGet パッケージ参照を Grpc.AspNetCore に追加します。
  • .proto ファイルを追加します。

Unix ドメイン ソケットを構成する

異なるマシン上のクライアントとサービス間の gRPC 呼び出しは、通常、TCP ソケットを介して送信されます。 TCP は、ネットワークを介して通信するように設計されています。 Unix ドメイン ソケット (UDS) は、クライアントとサーバーが同じマシン上にある場合に TCP よりも効率的な、広くサポートされている IPC テクノロジです。 .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 のエンドポイントを構成します。
  • 指定されたパスを使用して UDS をリッスンするために、ListenUnixSocket を呼び出します。
  • HTTPS を使用するように構成されていない UDS エンドポイントを作成します。 HTTPS を有効にする方法については、Kestrel HTTPS エンドポイントの構成に関する記事を参照してください。

クライアントの構成

GrpcChannel では、カスタム トランスポート経由での gRPC 呼び出しがサポートされます。 チャネルが作成されると、カスタムの ConnectCallback がある SocketsHttpHandler で構成できます。 コールバックを使用すると、クライアントはカスタム トランスポート経由で接続を確立し、そのトランスポートを介して 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
    });
}

上記のコードを使用して作成されたチャネルの場合、Unix ドメイン ソケット経由で gRPC 呼び出しが送信されます。 他の IPC テクノロジのサポートは、Kestrel と SocketsHttpHandler の機能拡張を使用して実装できます。