Comunicación entre procesos con gRPC
Nota:
Esta no es la versión más reciente de este artículo. Para la versión actual, consulta la versión .NET 8 de este artículo.
Advertencia
Esta versión de ASP.NET Core ya no se admite. Para obtener más información, consulta la Directiva de soporte técnico de .NET y .NET Core. Para la versión actual, consulta la versión .NET 8 de este artículo.
Importante
Esta información hace referencia a un producto en versión preliminar, el cual puede sufrir importantes modificaciones antes de que se publique la versión comercial. Microsoft no proporciona ninguna garantía, expresa o implícita, con respecto a la información proporcionada aquí.
Para la versión actual, consulte la versión .NET 8 de este artículo.
Los procesos que se ejecutan en la misma máquina se pueden diseñar para comunicarse entre sí. Los sistemas operativos proporcionan tecnologías para permitir una comunicación entre procesos (IPC) rápida y eficaz. Ejemplos populares de tecnologías de IPC son los sockets de dominio de Unix y las canalizaciones con nombre.
.NET proporciona asistencia para la comunicación entre procesos mediante gRPC.
La compatibilidad integrada con canalizaciones con nombre en ASP.NET Core requiere .NET 8 o posterior.
Introducción
Las llamadas IPC se envían desde un cliente a un servidor. Para comunicarse entre aplicaciones en un equipo con gRPC, al menos una aplicación debe hospedar un servidor gRPC de ASP.NET Core.
Normalmente, un servidor gRPC de ASP.NET Core se crea a partir de la plantilla de gRPC. El archivo de proyecto creado por la plantilla usa Microsoft.NET.SDK.Web
como 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>
El valor del SDK Microsoft.NET.SDK.Web
agrega automáticamente una referencia al marco ASP.NET Core. La referencia permite que la aplicación use tipos de ASP.NET Core necesarios para hospedar un servidor.
También es posible agregar un servidor a proyectos existentes que no sean de ASP.NET Core, como servicios de Windows, aplicaciones WPF o aplicaciones WinForms. Para obtener más información, consulte Hospedaje de gRPC en proyectos que no son de ASP.NET Core.
Transportes de comunicación entre procesos (IPC)
Las llamadas gRPC entre un cliente y un servidor en equipos diferentes se suelen enviar mediante sockets TCP. TCP es una buena opción para comunicarse a través de una red o Internet. Pero los transportes IPC ofrecen ventajas al comunicarse entre procesos en la misma máquina:
- Menos sobrecarga y velocidades de transferencia más rápidas.
- Integración con las características de seguridad del sistema operativo.
- No usa puertos TCP, que son un recurso limitado.
.NET admite varios transportes IPC:
- Los sockets de dominio de Unix (UDS) son una tecnología IPC ampliamente compatible. UDS es la mejor opción para compilar aplicaciones multiplataforma y se puede usar en Linux, macOS y Windows 10/Windows Server 2019 o posterior.
- Todas las versiones de Windows admiten canalizaciones con nombre. Las canalizaciones con nombre se integran bien con la seguridad de Windows, que puede controlar el acceso del cliente a la canalización.
- Otros transportes IPC implementando IConnectionListenerFactory y registrando la implementación en el inicio de la aplicación.
En función del sistema operativo, las aplicaciones multiplataforma pueden elegir diferentes transportes IPC. Una aplicación puede comprobar el sistema operativo al iniciarse y elegir el transporte deseado para esa plataforma:
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;
});
});
Consideraciones sobre la seguridad
Las aplicaciones IPC envían y reciben llamadas RPC. La comunicación externa es un vector de ataque potencial para las aplicaciones IPC y se debe proteger correctamente.
Protección de la aplicación de servidor IPC contra autores de llamada inesperados
La aplicación de servidor IPC hospeda servicios RPC que otras aplicaciones pueden llamar. Los autores de la llamada entrantes se deben autenticar para evitar que los clientes que no sean de confianza realicen llamadas RPC al servidor.
La seguridad de transporte es una opción para proteger un servidor. Los transportes IPC, como los sockets de dominio Unix y las canalizaciones con nombre, admiten la limitación del acceso en función de los permisos del sistema operativo:
- Las canalizaciones con nombre admiten la protección de una canalización con el modelo de control de acceso de Windows. Los derechos de acceso se pueden configurar en .NET cuando se inicia un servidor mediante la clase PipeSecurity.
- Los sockets de dominio de Unix admiten la protección de un socket con permisos de archivo.
Otra opción para proteger un servidor IPC consiste en usar la autenticación y la autorización integradas en ASP.NET Core. Por ejemplo, el servidor se podría configurar para exigir la autenticación de certificados. Las llamadas RPC realizadas por las aplicaciones cliente sin el certificado necesario generan un error con una respuesta no autorizada.
Validación del servidor en la aplicación cliente IPC
Es importante que la aplicación cliente valide la identity del servidor al que llama. La validación es necesaria para protegerse y evitar que un actor malintencionado detenga el servidor de confianza, ejecute sus propios datos y aceptar los datos entrantes de los clientes.
Las canalizaciones con nombre proporcionan compatibilidad para obtener la cuenta en la que se ejecuta un servidor. Un cliente puede validar que la cuenta esperada ha iniciado el servidor:
internal static bool CheckPipeConnectionOwnership(
NamedPipeClientStream pipeStream, SecurityIdentifier expectedOwner)
{
var remotePipeSecurity = pipeStream.GetAccessControl();
var remoteOwner = remotePipeSecurity.GetOwner(typeof(SecurityIdentifier));
return expectedOwner.Equals(remoteOwner);
}
Otra opción para validar el servidor consiste en proteger sus puntos de conexión con HTTPS dentro de ASP.NET Core. El cliente puede configurar SocketsHttpHandler
para validar que el servidor usa el certificado esperado cuando se establece la conexión.
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.
}
}
};
Protección contra la elevación de privilegios de canalizaciones con nombre
Las canalizaciones con nombre admiten una característica denominada suplantación. Con la suplantación, el servidor de canalizaciones con nombre puede ejecutar código con los privilegios del usuario cliente. Se trata de una característica eficaz, pero puede permitir que un servidor con pocos privilegios suplante a un autor de la llamada con privilegios elevados y, después, ejecutar código malintencionado.
El cliente puede protegerse contra este ataque al no permitir la suplantación al conectarse a un servidor. A menos que sea necesario para un servidor, se debe usar un valor TokenImpersonationLevel de None
o Anonymous
al crear una conexión de cliente:
using var pipeClient = new NamedPipeClientStream(
serverName: ".", pipeName: "testpipe", PipeDirection.In, PipeOptions.None, TokenImpersonationLevel.None);
await pipeClient.ConnectAsync();
TokenImpersonationLevel.None
es el valor predeterminado en constructores NamedPipeClientStream
que no tienen un parámetro impersonationLevel
.
Configuración del cliente y del servidor
El cliente y el servidor deben configurarse para usar un transporte de comunicación entre procesos (IPC). Para obtener más información acerca de cómo configurar Kestrel y SocketsHttpHandler para usar IPC:
- Comunicación entre procesos con sockets de dominio gRPC y Unix
- Comunicación entre procesos con gRPC y canalizaciones con nombre
Nota
La compatibilidad integrada con canalizaciones con nombre en ASP.NET Core requiere .NET 8 o posterior.
Los procesos que se ejecutan en la misma máquina se pueden diseñar para comunicarse entre sí. Los sistemas operativos proporcionan tecnologías para permitir una comunicación entre procesos (IPC) rápida y eficaz. Ejemplos populares de tecnologías de IPC son los sockets de dominio de Unix y las canalizaciones con nombre.
.NET proporciona asistencia para la comunicación entre procesos mediante gRPC.
Nota:
La compatibilidad integrada con canalizaciones con nombre en ASP.NET Core requiere .NET 8 o posterior.
Para más información, consulte .NET 8 o una versión posterior de este tema.
Introducción
Las llamadas gRPC se envían desde un cliente a un servidor. Para comunicarse entre aplicaciones en un equipo con gRPC, al menos una aplicación debe hospedar un servidor gRPC de ASP.NET Core.
ASP.NET Core y gRPC se pueden hospedar en cualquier aplicación mediante .NET Core 3.1 o posterior agregando el marco de trabajo Microsoft.AspNetCore.App
al proyecto.
<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>
El archivo del proyecto anterior:
- Agrega una referencia de marco a
Microsoft.AspNetCore.App
. La referencia de marco permite a aplicaciones no ASP.NET Core, como servicios de Windows, aplicaciones WPF o aplicaciones WinForms, usar ASP.NET Core y hospedar un servidor ASP.NET Core. - Agrega una referencia de paquete NuGet a
Grpc.AspNetCore
. - Agrega un archivo
.proto
.
Configuración de sockets de dominio de Unix
Las llamadas gRPC entre un cliente y un servidor en equipos diferentes se suelen enviar mediante sockets TCP. TCP se ha diseñado para comunicarse a través de una red. Los sockets de dominio Unix (UDS) son una tecnología IPC ampliamente compatible que es más eficaz que TCP cuando el cliente y el servidor están en la misma máquina. .NET proporciona compatibilidad integrada con UDS en aplicaciones cliente y servidor.
Requisitos:
- .NET 5 o posterior
- Linux, macOS o Windows 10/Windows Server 2019 o posterior
Configuración del servidor
Los sockets de dominio Unix (UDS) son compatibles con Kestrel, que se configura en 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;
});
});
});
Ejemplo anterior:
- Se configuran los puntos de conexión de Kestrel en
ConfigureKestrel
. - Se llama a ListenUnixSocket para escuchar un UDS con la ruta de acceso especificada.
- Crea un punto de conexión de UDS que no está configurado para usar HTTPS. Para obtener información sobre cómo habilitar HTTPS, vea Configuración de puntos de conexión HTTPS para Kestrel.
Configuración de cliente
GrpcChannel
admite la realización de llamadas gRPC a través de transportes personalizados. Cuando se crea un canal, se puede configurar con un elemento SocketsHttpHandler
que tenga un elemento ConnectCallback
personalizado. La devolución de llamada permite al cliente realizar conexiones a través de transportes personalizados y, después, enviar solicitudes HTTP a través de ese transporte.
Ejemplo de fábrica de conexiones de sockets de dominio de 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 del generador de conexiones personalizadas para crear 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
});
}
Los canales creados con el código anterior envían llamadas gRPC a través de sockets de dominio de Unix. Se puede implementar la compatibilidad con otras tecnologías IPC por medio de la extensibilidad en Kestrel y SocketsHttpHandler
.