From 8bbf128212d2c188227758fdacf3ffe1e94b5105 Mon Sep 17 00:00:00 2001 From: Jan Krueger Date: Sun, 11 Jul 2021 01:33:24 +0200 Subject: [PATCH 1/9] feat(microservice): add tcp over tls support add tcp tls client options add tcp tls server options add tcp over tls support --- .../client/client-proxy-factory.ts | 10 +++++- packages/microservices/client/client-tcp.ts | 32 +++++++++++++++++-- packages/microservices/constants.ts | 1 + .../interfaces/client-metadata.interface.ts | 21 ++++++++---- .../microservice-configuration.interface.ts | 24 +++++++++----- packages/microservices/server/server-tcp.ts | 24 ++++++++++++-- .../test/client/client-tcp.spec.ts | 13 ++++++++ .../test/server/server-tcp.spec.ts | 20 ++++++++++++ 8 files changed, 124 insertions(+), 21 deletions(-) diff --git a/packages/microservices/client/client-proxy-factory.ts b/packages/microservices/client/client-proxy-factory.ts index f6bd8922716..d8fec711931 100644 --- a/packages/microservices/client/client-proxy-factory.ts +++ b/packages/microservices/client/client-proxy-factory.ts @@ -3,6 +3,7 @@ import { ClientOptions, CustomClientOptions, TcpClientOptions, + TcpTlsClientOptions, } from '../interfaces/client-metadata.interface'; import { Closeable } from '../interfaces/closeable.interface'; import { @@ -56,7 +57,14 @@ export class ClientProxyFactory { case Transport.KAFKA: return new ClientKafka(options as KafkaOptions['options']); default: - return new ClientTCP(options as TcpClientOptions['options']); + const uncheckedOptions = options as + | TcpClientOptions['options'] + | TcpTlsClientOptions['options']; + if (uncheckedOptions && uncheckedOptions.useTls === true) { + return new ClientTCP(options as TcpTlsClientOptions['options']); + } else { + return new ClientTCP(options as TcpClientOptions['options']); + } } } diff --git a/packages/microservices/client/client-tcp.ts b/packages/microservices/client/client-tcp.ts index 98b168868b5..80b9b45906f 100644 --- a/packages/microservices/client/client-tcp.ts +++ b/packages/microservices/client/client-tcp.ts @@ -2,6 +2,7 @@ import { Logger } from '@nestjs/common'; import * as net from 'net'; import { EmptyError, lastValueFrom } from 'rxjs'; import { share, tap } from 'rxjs/operators'; +import { TLSSocket } from 'tls'; import { CLOSE_EVENT, ECONNREFUSED, @@ -9,10 +10,14 @@ import { MESSAGE_EVENT, TCP_DEFAULT_HOST, TCP_DEFAULT_PORT, + TCP_DEFAULT_USE_TLS, } from '../constants'; import { JsonSocket } from '../helpers/json-socket'; import { PacketId, ReadPacket, WritePacket } from '../interfaces'; -import { TcpClientOptions } from '../interfaces/client-metadata.interface'; +import { + TcpClientOptions, + TcpTlsClientOptions, +} from '../interfaces/client-metadata.interface'; import { ClientProxy } from './client-proxy'; export class ClientTCP extends ClientProxy { @@ -20,13 +25,22 @@ export class ClientTCP extends ClientProxy { private readonly logger = new Logger(ClientTCP.name); private readonly port: number; private readonly host: string; + private readonly useTls: boolean; private isConnected = false; private socket: JsonSocket; - constructor(options: TcpClientOptions['options']) { + constructor(); + constructor(options: TcpClientOptions['options']); + constructor(options: TcpTlsClientOptions['options']); + constructor( + private readonly options?: + | TcpClientOptions['options'] + | TcpTlsClientOptions['options'], + ) { super(); this.port = this.getOptionsProp(options, 'port') || TCP_DEFAULT_PORT; this.host = this.getOptionsProp(options, 'host') || TCP_DEFAULT_HOST; + this.useTls = this.getOptionsProp(options, 'useTls') || TCP_DEFAULT_USE_TLS; this.initializeSerializer(options); this.initializeDeserializer(options); @@ -81,7 +95,19 @@ export class ClientTCP extends ClientProxy { } public createSocket(): JsonSocket { - return new JsonSocket(new net.Socket()); + let socket: net.Socket | TLSSocket = new net.Socket(); + + /** + * TLS enabled, "upgrade" the TCP Socket to TLS + */ + if (this.useTls === true) { + /** + * Options are TcpTlsClientOptions + */ + const options = this.options as TcpTlsClientOptions['options']; + socket = new TLSSocket(socket, options); + } + return new JsonSocket(socket); } public close() { diff --git a/packages/microservices/constants.ts b/packages/microservices/constants.ts index 79c5ea54c07..29631bb0ca7 100644 --- a/packages/microservices/constants.ts +++ b/packages/microservices/constants.ts @@ -2,6 +2,7 @@ import { ROUTE_ARGS_METADATA } from '@nestjs/common/constants'; export const TCP_DEFAULT_PORT = 3000; export const TCP_DEFAULT_HOST = 'localhost'; +export const TCP_DEFAULT_USE_TLS = false; export const REDIS_DEFAULT_URL = 'redis://localhost:6379'; export const NATS_DEFAULT_URL = 'nats://localhost:4222'; export const MQTT_DEFAULT_URL = 'mqtt://localhost:1883'; diff --git a/packages/microservices/interfaces/client-metadata.interface.ts b/packages/microservices/interfaces/client-metadata.interface.ts index 4962eecc12d..253f5cd00ab 100644 --- a/packages/microservices/interfaces/client-metadata.interface.ts +++ b/packages/microservices/interfaces/client-metadata.interface.ts @@ -11,6 +11,7 @@ import { RmqOptions, } from './microservice-configuration.interface'; import { Serializer } from './serializer.interface'; +import { TLSSocketOptions } from 'tls'; export type ClientOptions = | RedisOptions @@ -19,6 +20,7 @@ export type ClientOptions = | GrpcOptions | KafkaOptions | TcpClientOptions + | TcpTlsClientOptions | RmqOptions; export interface CustomClientOptions { @@ -26,12 +28,19 @@ export interface CustomClientOptions { options?: Record; } +export interface TcpClientBaseOptions { + host?: string; + port?: number; + serializer?: Serializer; + deserializer?: Deserializer; +} + export interface TcpClientOptions { transport: Transport.TCP; - options?: { - host?: string; - port?: number; - serializer?: Serializer; - deserializer?: Deserializer; - }; + options?: TcpClientBaseOptions & { useTls?: false | undefined }; +} + +export interface TcpTlsClientOptions { + transport: Transport.TCP; + options: TcpClientBaseOptions & { useTls: true } & TLSSocketOptions; } diff --git a/packages/microservices/interfaces/microservice-configuration.interface.ts b/packages/microservices/interfaces/microservice-configuration.interface.ts index d0d902675cd..8c6b282ed97 100644 --- a/packages/microservices/interfaces/microservice-configuration.interface.ts +++ b/packages/microservices/interfaces/microservice-configuration.interface.ts @@ -1,3 +1,4 @@ +import { TlsOptions } from 'tls'; import { Transport } from '../enums/transport.enum'; import { ChannelOptions } from '../external/grpc-options.interface'; import { @@ -67,16 +68,23 @@ export interface GrpcOptions { }; } +interface TcpBaseOptions { + host?: string; + port?: number; + retryAttempts?: number; + retryDelay?: number; + serializer?: Serializer; + deserializer?: Deserializer; +} + export interface TcpOptions { transport?: Transport.TCP; - options?: { - host?: string; - port?: number; - retryAttempts?: number; - retryDelay?: number; - serializer?: Serializer; - deserializer?: Deserializer; - }; + options?: TcpBaseOptions & { useTls?: false | undefined }; +} + +export interface TcpTlsOptions { + transport?: Transport.TCP; + options: TcpBaseOptions & { useTls: true } & TlsOptions; } export interface RedisOptions { diff --git a/packages/microservices/server/server-tcp.ts b/packages/microservices/server/server-tcp.ts index 6521ac960f2..bf886845176 100644 --- a/packages/microservices/server/server-tcp.ts +++ b/packages/microservices/server/server-tcp.ts @@ -1,5 +1,6 @@ import { isString, isUndefined } from '@nestjs/common/utils/shared.utils'; import * as net from 'net'; +import * as tls from 'tls'; import { Server as NetSocket, Socket } from 'net'; import { Observable } from 'rxjs'; import { @@ -11,6 +12,7 @@ import { NO_MESSAGE_HANDLER, TCP_DEFAULT_HOST, TCP_DEFAULT_PORT, + TCP_DEFAULT_USE_TLS, } from '../constants'; import { TcpContext } from '../ctx-host/tcp.context'; import { Transport } from '../enums'; @@ -22,7 +24,10 @@ import { ReadPacket, WritePacket, } from '../interfaces'; -import { TcpOptions } from '../interfaces/microservice-configuration.interface'; +import { + TcpOptions, + TcpTlsOptions, +} from '../interfaces/microservice-configuration.interface'; import { Server } from './server'; export class ServerTCP extends Server implements CustomTransportStrategy { @@ -30,14 +35,20 @@ export class ServerTCP extends Server implements CustomTransportStrategy { private readonly port: number; private readonly host: string; + private readonly useTls: boolean; private server: NetSocket; private isExplicitlyTerminated = false; private retryAttemptsCount = 0; - constructor(private readonly options: TcpOptions['options']) { + constructor(options: TcpOptions['options']); + constructor(options: TcpTlsOptions['options']); + constructor( + private readonly options: TcpOptions['options'] | TcpTlsOptions['options'], + ) { super(); this.port = this.getOptionsProp(options, 'port') || TCP_DEFAULT_PORT; this.host = this.getOptionsProp(options, 'host') || TCP_DEFAULT_HOST; + this.useTls = this.getOptionsProp(options, 'useTls') || TCP_DEFAULT_USE_TLS; this.init(); this.initializeSerializer(options); @@ -119,7 +130,14 @@ export class ServerTCP extends Server implements CustomTransportStrategy { } private init() { - this.server = net.createServer(this.bindHandler.bind(this)); + if (this.useTls) { + // TLS enabled, use tls server + const options = this.options as TcpTlsOptions['options']; + this.server = tls.createServer(options, this.bindHandler.bind(this)); + } else { + // TLS disabled, use net server + this.server = net.createServer(this.bindHandler.bind(this)); + } this.server.on(ERROR_EVENT, this.handleError.bind(this)); this.server.on(CLOSE_EVENT, this.handleClose.bind(this)); } diff --git a/packages/microservices/test/client/client-tcp.spec.ts b/packages/microservices/test/client/client-tcp.spec.ts index b49120b8176..b1cc65ed32e 100644 --- a/packages/microservices/test/client/client-tcp.spec.ts +++ b/packages/microservices/test/client/client-tcp.spec.ts @@ -1,5 +1,7 @@ import { expect } from 'chai'; +import { Socket as NetSocket } from 'net'; import * as sinon from 'sinon'; +import { TLSSocket } from 'tls'; import { ClientTCP } from '../../client/client-tcp'; import { ERROR_EVENT } from '../../constants'; @@ -197,4 +199,15 @@ describe('ClientTCP', () => { expect(sendMessageStub.called).to.be.true; }); }); + + // describe('tls', () => { + // it('should upgrade to TLS', () => { + // const jsonSocket = new ClientTCP({ useTls: true }).createSocket(); + // expect(jsonSocket.socket).instanceOf(TLSSocket); + // }); + // it('should not upgrade to TLS, if not requested', () => { + // const jsonSocket = new ClientTCP({ useTls: false }).createSocket(); + // expect(jsonSocket.socket).instanceOf(NetSocket); + // }); + // }); }); diff --git a/packages/microservices/test/server/server-tcp.spec.ts b/packages/microservices/test/server/server-tcp.spec.ts index e36aa3a73b0..afa64b5b39e 100644 --- a/packages/microservices/test/server/server-tcp.spec.ts +++ b/packages/microservices/test/server/server-tcp.spec.ts @@ -1,5 +1,7 @@ import { expect } from 'chai'; import * as sinon from 'sinon'; +import { Server as TLSServer } from 'tls'; +import { Server as NetServer } from 'net'; import { NO_MESSAGE_HANDLER } from '../../constants'; import { BaseRpcContext } from '../../ctx-host/base-rpc.context'; import { ServerTCP } from '../../server/server-tcp'; @@ -136,4 +138,22 @@ describe('ServerTCP', () => { expect(handler.calledWith(data)).to.be.true; }); }); + + describe('tls', () => { + it('should enable TLS when set', () => { + const server = new ServerTCP({ + useTls: true, + }); + /** + * Expect the server to be a instance of tls.Server + */ + expect(server['server']).instanceOf(TLSServer); + }); + it('should not use TLS when not requested', () => { + const server = new ServerTCP({ + useTls: false, + }); + expect(server['server']).instanceOf(NetServer); + }); + }); }); From 567c90236e86b01f1404971298be18b23a04397b Mon Sep 17 00:00:00 2001 From: Jan Krueger Date: Sun, 11 Jul 2021 01:40:16 +0200 Subject: [PATCH 2/9] feat(microservice): add tcp over tls support add tcp tls client options add tcp tls server options add tcp over tls support --- .../client/client-proxy-factory.ts | 10 ++++- packages/microservices/client/client-proxy.ts | 37 +++++++++++-------- packages/microservices/client/client-tcp.ts | 32 ++++++++++++++-- packages/microservices/constants.ts | 1 + .../interfaces/client-metadata.interface.ts | 21 ++++++++--- .../microservice-configuration.interface.ts | 24 ++++++++---- packages/microservices/server/server-tcp.ts | 24 ++++++++++-- .../test/client/client-tcp.spec.ts | 13 +++++++ .../test/server/server-tcp.spec.ts | 20 ++++++++++ 9 files changed, 146 insertions(+), 36 deletions(-) diff --git a/packages/microservices/client/client-proxy-factory.ts b/packages/microservices/client/client-proxy-factory.ts index f6bd8922716..d8fec711931 100644 --- a/packages/microservices/client/client-proxy-factory.ts +++ b/packages/microservices/client/client-proxy-factory.ts @@ -3,6 +3,7 @@ import { ClientOptions, CustomClientOptions, TcpClientOptions, + TcpTlsClientOptions, } from '../interfaces/client-metadata.interface'; import { Closeable } from '../interfaces/closeable.interface'; import { @@ -56,7 +57,14 @@ export class ClientProxyFactory { case Transport.KAFKA: return new ClientKafka(options as KafkaOptions['options']); default: - return new ClientTCP(options as TcpClientOptions['options']); + const uncheckedOptions = options as + | TcpClientOptions['options'] + | TcpTlsClientOptions['options']; + if (uncheckedOptions && uncheckedOptions.useTls === true) { + return new ClientTCP(options as TcpTlsClientOptions['options']); + } else { + return new ClientTCP(options as TcpClientOptions['options']); + } } } diff --git a/packages/microservices/client/client-proxy.ts b/packages/microservices/client/client-proxy.ts index cdc477dd1b9..899ee223e93 100644 --- a/packages/microservices/client/client-proxy.ts +++ b/packages/microservices/client/client-proxy.ts @@ -25,6 +25,7 @@ import { RedisOptions, RmqOptions, TcpClientOptions, + TcpTlsClientOptions, WritePacket, } from '../interfaces'; import { ProducerDeserializer } from '../interfaces/deserializer.interface'; @@ -128,7 +129,7 @@ export abstract class ClientProxy { protected getOptionsProp< T extends ClientOptions['options'], - K extends keyof T + K extends keyof T, >(obj: T, prop: K, defaultValue: T[K] = undefined) { return (obj && obj[prop]) || defaultValue; } @@ -140,26 +141,32 @@ export abstract class ClientProxy { protected initializeSerializer(options: ClientOptions['options']) { this.serializer = (options && - (options as - | RedisOptions['options'] - | NatsOptions['options'] - | MqttOptions['options'] - | TcpClientOptions['options'] - | RmqOptions['options'] - | KafkaOptions['options']).serializer) || + ( + options as + | RedisOptions['options'] + | NatsOptions['options'] + | MqttOptions['options'] + | TcpClientOptions['options'] + | TcpTlsClientOptions['options'] + | RmqOptions['options'] + | KafkaOptions['options'] + ).serializer) || new IdentitySerializer(); } protected initializeDeserializer(options: ClientOptions['options']) { this.deserializer = (options && - (options as - | RedisOptions['options'] - | NatsOptions['options'] - | MqttOptions['options'] - | TcpClientOptions['options'] - | RmqOptions['options'] - | KafkaOptions['options']).deserializer) || + ( + options as + | RedisOptions['options'] + | NatsOptions['options'] + | MqttOptions['options'] + | TcpClientOptions['options'] + | TcpTlsClientOptions['options'] + | RmqOptions['options'] + | KafkaOptions['options'] + ).deserializer) || new IncomingResponseDeserializer(); } } diff --git a/packages/microservices/client/client-tcp.ts b/packages/microservices/client/client-tcp.ts index 98b168868b5..80b9b45906f 100644 --- a/packages/microservices/client/client-tcp.ts +++ b/packages/microservices/client/client-tcp.ts @@ -2,6 +2,7 @@ import { Logger } from '@nestjs/common'; import * as net from 'net'; import { EmptyError, lastValueFrom } from 'rxjs'; import { share, tap } from 'rxjs/operators'; +import { TLSSocket } from 'tls'; import { CLOSE_EVENT, ECONNREFUSED, @@ -9,10 +10,14 @@ import { MESSAGE_EVENT, TCP_DEFAULT_HOST, TCP_DEFAULT_PORT, + TCP_DEFAULT_USE_TLS, } from '../constants'; import { JsonSocket } from '../helpers/json-socket'; import { PacketId, ReadPacket, WritePacket } from '../interfaces'; -import { TcpClientOptions } from '../interfaces/client-metadata.interface'; +import { + TcpClientOptions, + TcpTlsClientOptions, +} from '../interfaces/client-metadata.interface'; import { ClientProxy } from './client-proxy'; export class ClientTCP extends ClientProxy { @@ -20,13 +25,22 @@ export class ClientTCP extends ClientProxy { private readonly logger = new Logger(ClientTCP.name); private readonly port: number; private readonly host: string; + private readonly useTls: boolean; private isConnected = false; private socket: JsonSocket; - constructor(options: TcpClientOptions['options']) { + constructor(); + constructor(options: TcpClientOptions['options']); + constructor(options: TcpTlsClientOptions['options']); + constructor( + private readonly options?: + | TcpClientOptions['options'] + | TcpTlsClientOptions['options'], + ) { super(); this.port = this.getOptionsProp(options, 'port') || TCP_DEFAULT_PORT; this.host = this.getOptionsProp(options, 'host') || TCP_DEFAULT_HOST; + this.useTls = this.getOptionsProp(options, 'useTls') || TCP_DEFAULT_USE_TLS; this.initializeSerializer(options); this.initializeDeserializer(options); @@ -81,7 +95,19 @@ export class ClientTCP extends ClientProxy { } public createSocket(): JsonSocket { - return new JsonSocket(new net.Socket()); + let socket: net.Socket | TLSSocket = new net.Socket(); + + /** + * TLS enabled, "upgrade" the TCP Socket to TLS + */ + if (this.useTls === true) { + /** + * Options are TcpTlsClientOptions + */ + const options = this.options as TcpTlsClientOptions['options']; + socket = new TLSSocket(socket, options); + } + return new JsonSocket(socket); } public close() { diff --git a/packages/microservices/constants.ts b/packages/microservices/constants.ts index 79c5ea54c07..29631bb0ca7 100644 --- a/packages/microservices/constants.ts +++ b/packages/microservices/constants.ts @@ -2,6 +2,7 @@ import { ROUTE_ARGS_METADATA } from '@nestjs/common/constants'; export const TCP_DEFAULT_PORT = 3000; export const TCP_DEFAULT_HOST = 'localhost'; +export const TCP_DEFAULT_USE_TLS = false; export const REDIS_DEFAULT_URL = 'redis://localhost:6379'; export const NATS_DEFAULT_URL = 'nats://localhost:4222'; export const MQTT_DEFAULT_URL = 'mqtt://localhost:1883'; diff --git a/packages/microservices/interfaces/client-metadata.interface.ts b/packages/microservices/interfaces/client-metadata.interface.ts index 4962eecc12d..253f5cd00ab 100644 --- a/packages/microservices/interfaces/client-metadata.interface.ts +++ b/packages/microservices/interfaces/client-metadata.interface.ts @@ -11,6 +11,7 @@ import { RmqOptions, } from './microservice-configuration.interface'; import { Serializer } from './serializer.interface'; +import { TLSSocketOptions } from 'tls'; export type ClientOptions = | RedisOptions @@ -19,6 +20,7 @@ export type ClientOptions = | GrpcOptions | KafkaOptions | TcpClientOptions + | TcpTlsClientOptions | RmqOptions; export interface CustomClientOptions { @@ -26,12 +28,19 @@ export interface CustomClientOptions { options?: Record; } +export interface TcpClientBaseOptions { + host?: string; + port?: number; + serializer?: Serializer; + deserializer?: Deserializer; +} + export interface TcpClientOptions { transport: Transport.TCP; - options?: { - host?: string; - port?: number; - serializer?: Serializer; - deserializer?: Deserializer; - }; + options?: TcpClientBaseOptions & { useTls?: false | undefined }; +} + +export interface TcpTlsClientOptions { + transport: Transport.TCP; + options: TcpClientBaseOptions & { useTls: true } & TLSSocketOptions; } diff --git a/packages/microservices/interfaces/microservice-configuration.interface.ts b/packages/microservices/interfaces/microservice-configuration.interface.ts index d0d902675cd..8c6b282ed97 100644 --- a/packages/microservices/interfaces/microservice-configuration.interface.ts +++ b/packages/microservices/interfaces/microservice-configuration.interface.ts @@ -1,3 +1,4 @@ +import { TlsOptions } from 'tls'; import { Transport } from '../enums/transport.enum'; import { ChannelOptions } from '../external/grpc-options.interface'; import { @@ -67,16 +68,23 @@ export interface GrpcOptions { }; } +interface TcpBaseOptions { + host?: string; + port?: number; + retryAttempts?: number; + retryDelay?: number; + serializer?: Serializer; + deserializer?: Deserializer; +} + export interface TcpOptions { transport?: Transport.TCP; - options?: { - host?: string; - port?: number; - retryAttempts?: number; - retryDelay?: number; - serializer?: Serializer; - deserializer?: Deserializer; - }; + options?: TcpBaseOptions & { useTls?: false | undefined }; +} + +export interface TcpTlsOptions { + transport?: Transport.TCP; + options: TcpBaseOptions & { useTls: true } & TlsOptions; } export interface RedisOptions { diff --git a/packages/microservices/server/server-tcp.ts b/packages/microservices/server/server-tcp.ts index 6521ac960f2..bf886845176 100644 --- a/packages/microservices/server/server-tcp.ts +++ b/packages/microservices/server/server-tcp.ts @@ -1,5 +1,6 @@ import { isString, isUndefined } from '@nestjs/common/utils/shared.utils'; import * as net from 'net'; +import * as tls from 'tls'; import { Server as NetSocket, Socket } from 'net'; import { Observable } from 'rxjs'; import { @@ -11,6 +12,7 @@ import { NO_MESSAGE_HANDLER, TCP_DEFAULT_HOST, TCP_DEFAULT_PORT, + TCP_DEFAULT_USE_TLS, } from '../constants'; import { TcpContext } from '../ctx-host/tcp.context'; import { Transport } from '../enums'; @@ -22,7 +24,10 @@ import { ReadPacket, WritePacket, } from '../interfaces'; -import { TcpOptions } from '../interfaces/microservice-configuration.interface'; +import { + TcpOptions, + TcpTlsOptions, +} from '../interfaces/microservice-configuration.interface'; import { Server } from './server'; export class ServerTCP extends Server implements CustomTransportStrategy { @@ -30,14 +35,20 @@ export class ServerTCP extends Server implements CustomTransportStrategy { private readonly port: number; private readonly host: string; + private readonly useTls: boolean; private server: NetSocket; private isExplicitlyTerminated = false; private retryAttemptsCount = 0; - constructor(private readonly options: TcpOptions['options']) { + constructor(options: TcpOptions['options']); + constructor(options: TcpTlsOptions['options']); + constructor( + private readonly options: TcpOptions['options'] | TcpTlsOptions['options'], + ) { super(); this.port = this.getOptionsProp(options, 'port') || TCP_DEFAULT_PORT; this.host = this.getOptionsProp(options, 'host') || TCP_DEFAULT_HOST; + this.useTls = this.getOptionsProp(options, 'useTls') || TCP_DEFAULT_USE_TLS; this.init(); this.initializeSerializer(options); @@ -119,7 +130,14 @@ export class ServerTCP extends Server implements CustomTransportStrategy { } private init() { - this.server = net.createServer(this.bindHandler.bind(this)); + if (this.useTls) { + // TLS enabled, use tls server + const options = this.options as TcpTlsOptions['options']; + this.server = tls.createServer(options, this.bindHandler.bind(this)); + } else { + // TLS disabled, use net server + this.server = net.createServer(this.bindHandler.bind(this)); + } this.server.on(ERROR_EVENT, this.handleError.bind(this)); this.server.on(CLOSE_EVENT, this.handleClose.bind(this)); } diff --git a/packages/microservices/test/client/client-tcp.spec.ts b/packages/microservices/test/client/client-tcp.spec.ts index b49120b8176..b1cc65ed32e 100644 --- a/packages/microservices/test/client/client-tcp.spec.ts +++ b/packages/microservices/test/client/client-tcp.spec.ts @@ -1,5 +1,7 @@ import { expect } from 'chai'; +import { Socket as NetSocket } from 'net'; import * as sinon from 'sinon'; +import { TLSSocket } from 'tls'; import { ClientTCP } from '../../client/client-tcp'; import { ERROR_EVENT } from '../../constants'; @@ -197,4 +199,15 @@ describe('ClientTCP', () => { expect(sendMessageStub.called).to.be.true; }); }); + + // describe('tls', () => { + // it('should upgrade to TLS', () => { + // const jsonSocket = new ClientTCP({ useTls: true }).createSocket(); + // expect(jsonSocket.socket).instanceOf(TLSSocket); + // }); + // it('should not upgrade to TLS, if not requested', () => { + // const jsonSocket = new ClientTCP({ useTls: false }).createSocket(); + // expect(jsonSocket.socket).instanceOf(NetSocket); + // }); + // }); }); diff --git a/packages/microservices/test/server/server-tcp.spec.ts b/packages/microservices/test/server/server-tcp.spec.ts index e36aa3a73b0..afa64b5b39e 100644 --- a/packages/microservices/test/server/server-tcp.spec.ts +++ b/packages/microservices/test/server/server-tcp.spec.ts @@ -1,5 +1,7 @@ import { expect } from 'chai'; import * as sinon from 'sinon'; +import { Server as TLSServer } from 'tls'; +import { Server as NetServer } from 'net'; import { NO_MESSAGE_HANDLER } from '../../constants'; import { BaseRpcContext } from '../../ctx-host/base-rpc.context'; import { ServerTCP } from '../../server/server-tcp'; @@ -136,4 +138,22 @@ describe('ServerTCP', () => { expect(handler.calledWith(data)).to.be.true; }); }); + + describe('tls', () => { + it('should enable TLS when set', () => { + const server = new ServerTCP({ + useTls: true, + }); + /** + * Expect the server to be a instance of tls.Server + */ + expect(server['server']).instanceOf(TLSServer); + }); + it('should not use TLS when not requested', () => { + const server = new ServerTCP({ + useTls: false, + }); + expect(server['server']).instanceOf(NetServer); + }); + }); }); From ae124a9ef6f952575438e018dc49de958b2567dd Mon Sep 17 00:00:00 2001 From: Jan Krueger Date: Sun, 11 Jul 2021 14:00:56 +0200 Subject: [PATCH 3/9] feat(microservice): add tcp over tls support fix client-tcp not beeing able to handle empty options --- packages/microservices/client/client-tcp.ts | 3 +++ packages/microservices/interfaces/client-metadata.interface.ts | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/microservices/client/client-tcp.ts b/packages/microservices/client/client-tcp.ts index 80b9b45906f..62fa8dced18 100644 --- a/packages/microservices/client/client-tcp.ts +++ b/packages/microservices/client/client-tcp.ts @@ -38,6 +38,9 @@ export class ClientTCP extends ClientProxy { | TcpTlsClientOptions['options'], ) { super(); + if (options === undefined) { + this.options = {}; + } this.port = this.getOptionsProp(options, 'port') || TCP_DEFAULT_PORT; this.host = this.getOptionsProp(options, 'host') || TCP_DEFAULT_HOST; this.useTls = this.getOptionsProp(options, 'useTls') || TCP_DEFAULT_USE_TLS; diff --git a/packages/microservices/interfaces/client-metadata.interface.ts b/packages/microservices/interfaces/client-metadata.interface.ts index 253f5cd00ab..0284cba7897 100644 --- a/packages/microservices/interfaces/client-metadata.interface.ts +++ b/packages/microservices/interfaces/client-metadata.interface.ts @@ -37,7 +37,7 @@ export interface TcpClientBaseOptions { export interface TcpClientOptions { transport: Transport.TCP; - options?: TcpClientBaseOptions & { useTls?: false | undefined }; + options?: (TcpClientBaseOptions & { useTls?: false | undefined }) | undefined; } export interface TcpTlsClientOptions { From 0cf63a32fc3851a727b7bad8e059e65fd0efd6f3 Mon Sep 17 00:00:00 2001 From: Jan Krueger Date: Sun, 11 Jul 2021 14:01:43 +0200 Subject: [PATCH 4/9] feat(microservice): add tcp over tls support fix isCustomClientOptions type-guard not beeing able to handle undefined --- packages/microservices/client/client-proxy-factory.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/microservices/client/client-proxy-factory.ts b/packages/microservices/client/client-proxy-factory.ts index d8fec711931..41f390f2e16 100644 --- a/packages/microservices/client/client-proxy-factory.ts +++ b/packages/microservices/client/client-proxy-factory.ts @@ -59,18 +59,21 @@ export class ClientProxyFactory { default: const uncheckedOptions = options as | TcpClientOptions['options'] - | TcpTlsClientOptions['options']; + | TcpTlsClientOptions['options'] + | undefined; if (uncheckedOptions && uncheckedOptions.useTls === true) { return new ClientTCP(options as TcpTlsClientOptions['options']); } else { - return new ClientTCP(options as TcpClientOptions['options']); + return new ClientTCP( + options as TcpClientOptions['options'] | undefined, + ); } } } private static isCustomClientOptions( - options: ClientOptions | CustomClientOptions, + options: ClientOptions | CustomClientOptions | undefined | null, ): options is CustomClientOptions { - return !!(options as CustomClientOptions).customClass; + return options && !!(options as CustomClientOptions).customClass; } } From 18896a5f149a1d2b387f727ba1ea85acfe15b108 Mon Sep 17 00:00:00 2001 From: Jan Krueger Date: Sun, 11 Jul 2021 14:33:32 +0200 Subject: [PATCH 5/9] feat(microservice): add tcp over tls support add TcpTlsOptions to MicroserviceOptions type --- .../interfaces/microservice-configuration.interface.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/microservices/interfaces/microservice-configuration.interface.ts b/packages/microservices/interfaces/microservice-configuration.interface.ts index 8c6b282ed97..66d1b9a313f 100644 --- a/packages/microservices/interfaces/microservice-configuration.interface.ts +++ b/packages/microservices/interfaces/microservice-configuration.interface.ts @@ -19,6 +19,7 @@ import { Serializer } from './serializer.interface'; export type MicroserviceOptions = | GrpcOptions | TcpOptions + | TcpTlsOptions | RedisOptions | NatsOptions | MqttOptions From 18eb60d110886c126045cdb56f55961b0165c883 Mon Sep 17 00:00:00 2001 From: Jan Krueger Date: Mon, 12 Jul 2021 12:22:51 +0200 Subject: [PATCH 6/9] feat(microservice): add tcp over tls support fix client not connecting when using tls --- packages/microservices/client/client-tcp.ts | 55 +++++++++++++++------ packages/microservices/constants.ts | 1 + 2 files changed, 41 insertions(+), 15 deletions(-) diff --git a/packages/microservices/client/client-tcp.ts b/packages/microservices/client/client-tcp.ts index 62fa8dced18..07362788ac9 100644 --- a/packages/microservices/client/client-tcp.ts +++ b/packages/microservices/client/client-tcp.ts @@ -1,8 +1,8 @@ import { Logger } from '@nestjs/common'; import * as net from 'net'; -import { EmptyError, lastValueFrom } from 'rxjs'; +import { EmptyError, lastValueFrom, Observable } from 'rxjs'; import { share, tap } from 'rxjs/operators'; -import { TLSSocket } from 'tls'; +import * as tls from 'tls'; import { CLOSE_EVENT, ECONNREFUSED, @@ -11,6 +11,7 @@ import { TCP_DEFAULT_HOST, TCP_DEFAULT_PORT, TCP_DEFAULT_USE_TLS, + TLS_CONNECT_EVENT, } from '../constants'; import { JsonSocket } from '../helpers/json-socket'; import { PacketId, ReadPacket, WritePacket } from '../interfaces'; @@ -29,6 +30,11 @@ export class ClientTCP extends ClientProxy { private isConnected = false; private socket: JsonSocket; + /** + * The underling netSocket used by the TLS Socket + */ + private netSocket: net.Socket | null = null; + constructor(); constructor(options: TcpClientOptions['options']); constructor(options: TcpTlsClientOptions['options']); @@ -56,17 +62,35 @@ export class ClientTCP extends ClientProxy { this.socket = this.createSocket(); this.bindEvents(this.socket); - const source$ = this.connect$(this.socket.netSocket).pipe( - tap(() => { - this.isConnected = true; - this.socket.on(MESSAGE_EVENT, (buffer: WritePacket & PacketId) => - this.handleResponse(buffer), - ); - }), - share(), - ); - - this.socket.connect(this.port, this.host); + let source$: Observable; + + if (this.useTls) { + this.netSocket.connect(this.port, this.host); + source$ = this.connect$( + this.socket.netSocket, + ERROR_EVENT, + TLS_CONNECT_EVENT, + ).pipe( + tap(() => { + this.isConnected = true; + this.socket.on(MESSAGE_EVENT, (buffer: WritePacket & PacketId) => + this.handleResponse(buffer), + ); + }), + share(), + ); + } else { + source$ = this.connect$(this.socket.netSocket).pipe( + tap(() => { + this.isConnected = true; + this.socket.on(MESSAGE_EVENT, (buffer: WritePacket & PacketId) => + this.handleResponse(buffer), + ); + }), + share(), + ); + this.socket.connect(this.port, this.host); + } this.connection = lastValueFrom(source$).catch(err => { if (err instanceof EmptyError) { return; @@ -98,7 +122,7 @@ export class ClientTCP extends ClientProxy { } public createSocket(): JsonSocket { - let socket: net.Socket | TLSSocket = new net.Socket(); + let socket: net.Socket | tls.TLSSocket = new net.Socket(); /** * TLS enabled, "upgrade" the TCP Socket to TLS @@ -108,7 +132,8 @@ export class ClientTCP extends ClientProxy { * Options are TcpTlsClientOptions */ const options = this.options as TcpTlsClientOptions['options']; - socket = new TLSSocket(socket, options); + this.netSocket = socket; + socket = tls.connect({ ...options, socket }); } return new JsonSocket(socket); } diff --git a/packages/microservices/constants.ts b/packages/microservices/constants.ts index 29631bb0ca7..5b701b8e14f 100644 --- a/packages/microservices/constants.ts +++ b/packages/microservices/constants.ts @@ -18,6 +18,7 @@ export const ERROR_EVENT = 'error'; export const CLOSE_EVENT = 'close'; export const SUBSCRIBE = 'subscribe'; export const CANCEL_EVENT = 'cancelled'; +export const TLS_CONNECT_EVENT = 'secureConnect'; export const PATTERN_METADATA = 'microservices:pattern'; export const TRANSPORT_METADATA = 'microservices:transport'; From 2d496643bcebe329f1b52cb7535030dfefb6b806 Mon Sep 17 00:00:00 2001 From: Jan Krueger Date: Mon, 17 Jan 2022 12:00:40 +0100 Subject: [PATCH 7/9] feat(microservice): add tcp over tls support --- .../client/client-proxy-factory.ts | 12 +--------- packages/microservices/client/client-tcp.ts | 24 +++++++++++-------- .../test/client/client-tcp.spec.ts | 20 ++++++++-------- 3 files changed, 25 insertions(+), 31 deletions(-) diff --git a/packages/microservices/client/client-proxy-factory.ts b/packages/microservices/client/client-proxy-factory.ts index 41f390f2e16..0dcf1a58786 100644 --- a/packages/microservices/client/client-proxy-factory.ts +++ b/packages/microservices/client/client-proxy-factory.ts @@ -57,17 +57,7 @@ export class ClientProxyFactory { case Transport.KAFKA: return new ClientKafka(options as KafkaOptions['options']); default: - const uncheckedOptions = options as - | TcpClientOptions['options'] - | TcpTlsClientOptions['options'] - | undefined; - if (uncheckedOptions && uncheckedOptions.useTls === true) { - return new ClientTCP(options as TcpTlsClientOptions['options']); - } else { - return new ClientTCP( - options as TcpClientOptions['options'] | undefined, - ); - } + return new ClientTCP(options as TcpClientOptions['options']); } } diff --git a/packages/microservices/client/client-tcp.ts b/packages/microservices/client/client-tcp.ts index 10f8f9b2932..bd184756734 100644 --- a/packages/microservices/client/client-tcp.ts +++ b/packages/microservices/client/client-tcp.ts @@ -30,27 +30,31 @@ export class ClientTCP extends ClientProxy { private isConnected = false; private socket: JsonSocket; + /** + * TLS Options, when TLS is being used + */ + private tlsOptions?: TcpTlsClientOptions['options']; + /** * The underling netSocket used by the TLS Socket */ private netSocket: net.Socket | null = null; - constructor(); - constructor(options: TcpClientOptions['options']); - constructor(options: TcpTlsClientOptions['options']); constructor( - private readonly options?: - | TcpClientOptions['options'] - | TcpTlsClientOptions['options'], + options: TcpClientOptions['options'] | TcpTlsClientOptions['options'], ) { super(); - if (options === undefined) { - this.options = {}; - } this.port = this.getOptionsProp(options, 'port') || TCP_DEFAULT_PORT; this.host = this.getOptionsProp(options, 'host') || TCP_DEFAULT_HOST; this.useTls = this.getOptionsProp(options, 'useTls') || TCP_DEFAULT_USE_TLS; + /** + * If TLS is being used, set the TLS Options + */ + if (this.useTls) { + this.tlsOptions = options as TcpTlsClientOptions['options']; + } + this.initializeSerializer(options); this.initializeDeserializer(options); } @@ -131,7 +135,7 @@ export class ClientTCP extends ClientProxy { /** * Options are TcpTlsClientOptions */ - const options = this.options as TcpTlsClientOptions['options']; + const options = this.tlsOptions as TcpTlsClientOptions['options']; this.netSocket = socket; socket = tls.connect({ ...options, socket }); } diff --git a/packages/microservices/test/client/client-tcp.spec.ts b/packages/microservices/test/client/client-tcp.spec.ts index 2b5a9f3caf3..b0e1c089d1b 100644 --- a/packages/microservices/test/client/client-tcp.spec.ts +++ b/packages/microservices/test/client/client-tcp.spec.ts @@ -200,14 +200,14 @@ describe('ClientTCP', () => { }); }); - // describe('tls', () => { - // it('should upgrade to TLS', () => { - // const jsonSocket = new ClientTCP({ useTls: true }).createSocket(); - // expect(jsonSocket.socket).instanceOf(TLSSocket); - // }); - // it('should not upgrade to TLS, if not requested', () => { - // const jsonSocket = new ClientTCP({ useTls: false }).createSocket(); - // expect(jsonSocket.socket).instanceOf(NetSocket); - // }); - // }); + describe('tls', () => { + it('should upgrade to TLS', () => { + const jsonSocket = new ClientTCP({ useTls: true }).createSocket(); + expect(jsonSocket.socket).instanceOf(TLSSocket); + }); + it('should not upgrade to TLS, if not requested', () => { + const jsonSocket = new ClientTCP({ useTls: false }).createSocket(); + expect(jsonSocket.socket).instanceOf(NetSocket); + }); + }); }); From a6e2b5f0ff515fb0eafd91691f9fd5e3b2e25e74 Mon Sep 17 00:00:00 2001 From: Jan Krueger Date: Mon, 17 Jan 2022 13:45:09 +0100 Subject: [PATCH 8/9] feat(microservice): add tcp over tls support add integration tests --- .../microservices/e2e/sum-rpc-tls.spec.ts | 148 ++++++++++++++++++ .../src/tcp-tls/app.controller.ts | 138 ++++++++++++++++ .../microservices/src/tcp-tls/app.module.ts | 93 +++++++++++ .../microservices/src/tcp-tls/ca.cert.pem | 22 +++ .../microservices/src/tcp-tls/fullchain.pem | 42 +++++ .../microservices/src/tcp-tls/privkey.pem | 27 ++++ 6 files changed, 470 insertions(+) create mode 100644 integration/microservices/e2e/sum-rpc-tls.spec.ts create mode 100644 integration/microservices/src/tcp-tls/app.controller.ts create mode 100644 integration/microservices/src/tcp-tls/app.module.ts create mode 100644 integration/microservices/src/tcp-tls/ca.cert.pem create mode 100644 integration/microservices/src/tcp-tls/fullchain.pem create mode 100644 integration/microservices/src/tcp-tls/privkey.pem diff --git a/integration/microservices/e2e/sum-rpc-tls.spec.ts b/integration/microservices/e2e/sum-rpc-tls.spec.ts new file mode 100644 index 00000000000..e7773d2c237 --- /dev/null +++ b/integration/microservices/e2e/sum-rpc-tls.spec.ts @@ -0,0 +1,148 @@ +import { INestApplication } from '@nestjs/common'; +import { Transport, TcpTlsOptions } from '@nestjs/microservices'; +import { Test } from '@nestjs/testing'; +import { expect } from 'chai'; +import * as request from 'supertest'; +import { AppController } from '../src/tcp-tls/app.controller'; +import { ApplicationModule } from '../src/tcp-tls/app.module'; +import * as fs from 'fs'; +import * as path from 'path'; + +describe('RPC TLS transport', () => { + let server; + let app: INestApplication; + let key: string; + let cert: string; + + before(() => { + // Generate a self-signed key pair + console.log(__dirname); + key = fs + .readFileSync(path.join(__dirname, '../src/tcp-tls/privkey.pem'), 'utf8') + .toString(); + cert = fs + .readFileSync( + path.join(__dirname, '../src/tcp-tls/fullchain.pem'), + 'utf8', + ) + .toString(); + }); + + beforeEach(async () => { + const module = await Test.createTestingModule({ + imports: [ApplicationModule], + }).compile(); + + app = module.createNestApplication(); + server = app.getHttpAdapter().getInstance(); + + app.connectMicroservice({ + transport: Transport.TCP, + options: { + host: '0.0.0.0', + useTls: true, + key: key, + cert: cert, + }, + }); + await app.startAllMicroservices(); + await app.init(); + }); + + it(`/POST`, () => { + return request(server) + .post('/?command=sum') + .send([1, 2, 3, 4, 5]) + .expect(200, '15'); + }); + + it(`/POST (Promise/async)`, () => { + return request(server) + .post('/?command=asyncSum') + .send([1, 2, 3, 4, 5]) + .expect(200) + .expect(200, '15'); + }); + + it(`/POST (Observable stream)`, () => { + return request(server) + .post('/?command=streamSum') + .send([1, 2, 3, 4, 5]) + .expect(200, '15'); + }); + + it(`/POST (useFactory client)`, () => { + return request(server) + .post('/useFactory?command=sum') + .send([1, 2, 3, 4, 5]) + .expect(200, '15'); + }); + + it(`/POST (useClass client)`, () => { + return request(server) + .post('/useClass?command=sum') + .send([1, 2, 3, 4, 5]) + .expect(200, '15'); + }); + + it(`/POST (concurrent)`, () => { + return request(server) + .post('/concurrent') + .send([ + Array.from({ length: 10 }, (v, k) => k + 1), + Array.from({ length: 10 }, (v, k) => k + 11), + Array.from({ length: 10 }, (v, k) => k + 21), + Array.from({ length: 10 }, (v, k) => k + 31), + Array.from({ length: 10 }, (v, k) => k + 41), + Array.from({ length: 10 }, (v, k) => k + 51), + Array.from({ length: 10 }, (v, k) => k + 61), + Array.from({ length: 10 }, (v, k) => k + 71), + Array.from({ length: 10 }, (v, k) => k + 81), + Array.from({ length: 10 }, (v, k) => k + 91), + ]) + .expect(200, 'true'); + }); + + it(`/POST (streaming)`, () => { + return request(server) + .post('/stream') + .send([1, 2, 3, 4, 5]) + .expect(200, '15'); + }); + + it(`/POST (pattern not found)`, () => { + return request(server).post('/?command=test').expect(500); + }); + + it(`/POST (event notification)`, done => { + request(server) + .post('/notify') + .send([1, 2, 3, 4, 5]) + .end(() => { + setTimeout(() => { + expect(AppController.IS_NOTIFIED).to.be.true; + done(); + }, 1000); + }); + }); + + it('/POST (custom client)', () => { + return request(server) + .post('/error?client=custom') + .send({}) + .expect(200) + .expect('true'); + }); + + it('/POST (standard client)', () => { + return request(server) + .post('/error?client=standard') + .send({}) + .expect(200) + .expect('false'); + }); + + afterEach(async () => { + await app.close(); + }); +}); diff --git a/integration/microservices/src/tcp-tls/app.controller.ts b/integration/microservices/src/tcp-tls/app.controller.ts new file mode 100644 index 00000000000..b52ac83c9e5 --- /dev/null +++ b/integration/microservices/src/tcp-tls/app.controller.ts @@ -0,0 +1,138 @@ +import { + Body, + Controller, + HttpCode, + Inject, + Post, + Query, +} from '@nestjs/common'; +import { + Client, + ClientProxy, + EventPattern, + MessagePattern, + RpcException, + Transport, +} from '@nestjs/microservices'; +import { from, lastValueFrom, Observable, of, throwError } from 'rxjs'; +import { catchError, scan } from 'rxjs/operators'; +import * as fs from 'fs'; +import * as path from 'path'; + +@Controller() +export class AppController { + constructor( + @Inject('USE_CLASS_CLIENT') private useClassClient: ClientProxy, + @Inject('USE_FACTORY_CLIENT') private useFactoryClient: ClientProxy, + @Inject('CUSTOM_PROXY_CLIENT') private customClient: ClientProxy, + ) {} + static IS_NOTIFIED = false; + + @Client({ + transport: Transport.TCP, + options: { + useTls: true, + ca: fs + .readFileSync(path.join(__dirname, 'ca.cert.pem'), 'utf-8') + .toString(), + }, + }) + client: ClientProxy; + + @Post() + @HttpCode(200) + call(@Query('command') cmd, @Body() data: number[]): Observable { + return this.client.send({ cmd }, data); + } + + @Post('useFactory') + @HttpCode(200) + callWithClientUseFactory( + @Query('command') cmd, + @Body() data: number[], + ): Observable { + return this.useFactoryClient.send({ cmd }, data); + } + + @Post('useClass') + @HttpCode(200) + callWithClientUseClass( + @Query('command') cmd, + @Body() data: number[], + ): Observable { + return this.useClassClient.send({ cmd }, data); + } + + @Post('stream') + @HttpCode(200) + stream(@Body() data: number[]): Observable { + return this.client + .send({ cmd: 'streaming' }, data) + .pipe(scan((a, b) => a + b)); + } + + @Post('concurrent') + @HttpCode(200) + concurrent(@Body() data: number[][]): Promise { + const send = async (tab: number[]) => { + const expected = tab.reduce((a, b) => a + b); + const result = await lastValueFrom( + this.client.send({ cmd: 'sum' }, tab), + ); + + return result === expected; + }; + return data + .map(async tab => send(tab)) + .reduce(async (a, b) => (await a) && b); + } + + @Post('error') + @HttpCode(200) + serializeError( + @Query('client') query: 'custom' | 'standard' = 'standard', + @Body() body: Record, + ): Observable { + const client = query === 'custom' ? this.customClient : this.client; + return client.send({ cmd: 'err' }, {}).pipe( + catchError(err => { + return of(err instanceof RpcException); + }), + ); + } + + @MessagePattern({ cmd: 'sum' }) + sum(data: number[]): number { + return (data || []).reduce((a, b) => a + b); + } + + @MessagePattern({ cmd: 'asyncSum' }) + async asyncSum(data: number[]): Promise { + return (data || []).reduce((a, b) => a + b); + } + + @MessagePattern({ cmd: 'streamSum' }) + streamSum(data: number[]): Observable { + return of((data || []).reduce((a, b) => a + b)); + } + + @MessagePattern({ cmd: 'streaming' }) + streaming(data: number[]): Observable { + return from(data); + } + + @MessagePattern({ cmd: 'err' }) + throwAnError() { + return throwError(() => new Error('err')); + } + + @Post('notify') + async sendNotification(): Promise { + return this.client.emit('notification', true); + } + + @EventPattern('notification') + eventHandler(data: boolean) { + AppController.IS_NOTIFIED = data; + } +} diff --git a/integration/microservices/src/tcp-tls/app.module.ts b/integration/microservices/src/tcp-tls/app.module.ts new file mode 100644 index 00000000000..56eafb3517f --- /dev/null +++ b/integration/microservices/src/tcp-tls/app.module.ts @@ -0,0 +1,93 @@ +import { Module, Injectable } from '@nestjs/common'; +import { AppController } from './app.controller'; +import { + ClientsModule, + Transport, + ClientsModuleOptionsFactory, + ClientOptions, + ClientTCP, + RpcException, +} from '@nestjs/microservices'; + +import * as fs from 'fs'; +import * as path from 'path'; + +const caCert = fs.readFileSync(path.join(__dirname, 'ca.cert.pem')).toString(); + +class ErrorHandlingProxy extends ClientTCP { + constructor() { + super({ + useTls: true, + ca: caCert, + }); + } + + serializeError(err) { + return new RpcException(err); + } +} + +@Injectable() +class ConfigService { + private readonly config = { + transport: Transport.TCP, + }; + get(key: string, defaultValue?: any) { + return this.config[key] || defaultValue; + } +} + +@Module({ + providers: [ConfigService], + exports: [ConfigService], +}) +class ConfigModule {} + +@Injectable() +class ClientOptionService implements ClientsModuleOptionsFactory { + constructor(private readonly configService: ConfigService) {} + createClientOptions(): Promise | ClientOptions { + return { + transport: this.configService.get('transport'), + options: { + useTls: true, + ca: caCert, + }, + }; + } +} + +@Module({ + imports: [ + ClientsModule.registerAsync([ + { + imports: [ConfigModule], + name: 'USE_FACTORY_CLIENT', + useFactory: (configService: ConfigService) => ({ + transport: configService.get('transport'), + options: { + useTls: true, + ca: caCert, + }, + }), + inject: [ConfigService], + }, + { + imports: [ConfigModule], + name: 'USE_CLASS_CLIENT', + useClass: ClientOptionService, + inject: [ConfigService], + }, + { + imports: [ConfigModule], + inject: [ConfigService], + name: 'CUSTOM_PROXY_CLIENT', + useFactory: (config: ConfigService) => ({ + customClass: ErrorHandlingProxy, + }), + }, + ]), + ], + controllers: [AppController], +}) +export class ApplicationModule {} diff --git a/integration/microservices/src/tcp-tls/ca.cert.pem b/integration/microservices/src/tcp-tls/ca.cert.pem new file mode 100644 index 00000000000..88eb0c54412 --- /dev/null +++ b/integration/microservices/src/tcp-tls/ca.cert.pem @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDrzCCApegAwIBAgIUI91x2hFgdVfOGeQ7HmWv9Gbs/BYwDQYJKoZIhvcNAQEL +BQAwZzELMAkGA1UEBhMCVVMxDTALBgNVBAgMBFV0YWgxDjAMBgNVBAcMBVByb3Zv +MSMwIQYDVQQKDBpBQ01FIFNpZ25pbmcgQXV0aG9yaXR5IEluYzEUMBIGA1UEAwwL +ZXhhbXBsZS5jb20wHhcNMjEwNzEyMDgzNDEwWhcNMjQwNTAxMDgzNDEwWjBnMQsw +CQYDVQQGEwJVUzENMAsGA1UECAwEVXRhaDEOMAwGA1UEBwwFUHJvdm8xIzAhBgNV +BAoMGkFDTUUgU2lnbmluZyBBdXRob3JpdHkgSW5jMRQwEgYDVQQDDAtleGFtcGxl +LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMVTRkOS/y5+4ZGu +kieLu2P/Y2/7ALoliDyxigYRrTsWe9DiITH4AEonL+sK+KOEKL/EsxtYKcGjvefF +Z9leutJNQOZh1kOkA3j8onS1ai969fb8xK7krTrCG5FYe7sR5RvqXqVd9WYTQ5pm +wQkabYgetETwGU/gvByoT4HGS0MaVxBTOvAj1jmxGEZ9S+j9jw4uDOVyqIjfW+20 +Rj5ft59fnOA1aku7rr0GPR178rWwe179IosbrSCJsFQ2HO/K+KkM2U7JgcBicMwD +nN/DD1hdR3ZlInOemDBpznnyYX6KG3hl8LE4/SJwLusan/n4ZlZswVxhD6OXwUyX +g9qFUgsCAwEAAaNTMFEwHQYDVR0OBBYEFKy1CFr9fjlDsMj9Tq4BaKUcGzCcMB8G +A1UdIwQYMBaAFKy1CFr9fjlDsMj9Tq4BaKUcGzCcMA8GA1UdEwEB/wQFMAMBAf8w +DQYJKoZIhvcNAQELBQADggEBAClDjogNzuuRXqZVzhZeGjaJVEf2ZDEzpRt24Olr +yBkiT9Zn2HeEgIXhduWh2OZmu5gJPVW6ZXzlzYJcdlHdyaaQJNQVXLZOMSihDn3A +GWKBl8RLmcyZFquW3UgC0KTsTiPhqmDkmC+0fnNSFLsizl8s2kVBvEa56MG2twCj +GewOCIbskx8MWlPbGgKEnZTwtD27/rfgP5V2EtjAGNIl84OZLln7/xrlyOZpoJha +R69OKXeniGUsYYvBNKcmHB+ApwHFUwDWMHAbw0vQicaolUwxLLE1L/Fa3OA2um6+ +ey6gCyLUNy6XoILHJ0ZqNluQWGofAsXNKc1vB52SMu0RNJU= +-----END CERTIFICATE----- diff --git a/integration/microservices/src/tcp-tls/fullchain.pem b/integration/microservices/src/tcp-tls/fullchain.pem new file mode 100644 index 00000000000..a00a8bbd0c7 --- /dev/null +++ b/integration/microservices/src/tcp-tls/fullchain.pem @@ -0,0 +1,42 @@ +-----BEGIN CERTIFICATE----- +MIIDRjCCAi4CFCDtvCFpWysgpFrfHwHT0nNJdhrLMA0GCSqGSIb3DQEBCwUAMGcx +CzAJBgNVBAYTAlVTMQ0wCwYDVQQIDARVdGFoMQ4wDAYDVQQHDAVQcm92bzEjMCEG +A1UECgwaQUNNRSBTaWduaW5nIEF1dGhvcml0eSBJbmMxFDASBgNVBAMMC2V4YW1w +bGUuY29tMB4XDTIxMDcxMjA4MzQxMFoXDTIyMTEyNDA4MzQxMFowWDELMAkGA1UE +BhMCVVMxDTALBgNVBAgMBFV0YWgxDjAMBgNVBAcMBVByb3ZvMRYwFAYDVQQKDA1B +Q01FIFRlY2ggSW5jMRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQDMctp7H7kLwp/42H/0XWwd7ctkZVv/w4tOU8CZCrKL +MeYn4TvUhuziJRUzLp8zcwaWVRaGgPN3YmNFpewruoXzm+6Sx0DLNhtd18eE4LZ2 +N1G1yKxjpmXCANSI+I1Fxgb7yN4Cca4t7KLHRr6oxT+ymv7txDnRmaVVPPnrD3c5 +m2SX3sS2pZecmKgvkpHq14UkpUdziMVC+2M4QMbvBv5YXE2gwOfrrP8naRQZKcNH +EDUoG7Q3i8tQhAf2zbeBcSDsjgRv3xcdOast5FB9qxUymgi7Qrui2KpxwTWiXsse +q2rT09NQp2sZYFRme2tdAGIV5BAYTr8WXqqpnaLLDhMdAgMBAAEwDQYJKoZIhvcN +AQELBQADggEBABxoFz9184LXr3N6jvp5YjlBQQGASMXsXXpG48yUkHgEuPpeBDAV +gkfNWK+0MNKh5LHUENNxm6ZMMhg2F3sSa+bSJMl8owcPmSqdDesn1uMbzcm2vxtg +cz9dflBTKeW4rwflctojwsfkvOD8NeFfgKhKSnL5ZneztHhk8xl+vv/ge87ERnrd +rka07ILAVgvZ6Q5zDbyLlzzWZ6XBYJpuaDB+5Aj0QF7a6/e0cFVUif+n/sCZ+7GE +sYVSqF3rdHRpJJDpBCS6mw+6z3x0Bpq+Wx1rsdVRwk420IXPejnbWNzpzpBTlV0J +OYWiGSo41KgTl3e9c62abFvz80NIra39bmQ= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDrzCCApegAwIBAgIUI91x2hFgdVfOGeQ7HmWv9Gbs/BYwDQYJKoZIhvcNAQEL +BQAwZzELMAkGA1UEBhMCVVMxDTALBgNVBAgMBFV0YWgxDjAMBgNVBAcMBVByb3Zv +MSMwIQYDVQQKDBpBQ01FIFNpZ25pbmcgQXV0aG9yaXR5IEluYzEUMBIGA1UEAwwL +ZXhhbXBsZS5jb20wHhcNMjEwNzEyMDgzNDEwWhcNMjQwNTAxMDgzNDEwWjBnMQsw +CQYDVQQGEwJVUzENMAsGA1UECAwEVXRhaDEOMAwGA1UEBwwFUHJvdm8xIzAhBgNV +BAoMGkFDTUUgU2lnbmluZyBBdXRob3JpdHkgSW5jMRQwEgYDVQQDDAtleGFtcGxl +LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMVTRkOS/y5+4ZGu +kieLu2P/Y2/7ALoliDyxigYRrTsWe9DiITH4AEonL+sK+KOEKL/EsxtYKcGjvefF +Z9leutJNQOZh1kOkA3j8onS1ai969fb8xK7krTrCG5FYe7sR5RvqXqVd9WYTQ5pm +wQkabYgetETwGU/gvByoT4HGS0MaVxBTOvAj1jmxGEZ9S+j9jw4uDOVyqIjfW+20 +Rj5ft59fnOA1aku7rr0GPR178rWwe179IosbrSCJsFQ2HO/K+KkM2U7JgcBicMwD +nN/DD1hdR3ZlInOemDBpznnyYX6KG3hl8LE4/SJwLusan/n4ZlZswVxhD6OXwUyX +g9qFUgsCAwEAAaNTMFEwHQYDVR0OBBYEFKy1CFr9fjlDsMj9Tq4BaKUcGzCcMB8G +A1UdIwQYMBaAFKy1CFr9fjlDsMj9Tq4BaKUcGzCcMA8GA1UdEwEB/wQFMAMBAf8w +DQYJKoZIhvcNAQELBQADggEBAClDjogNzuuRXqZVzhZeGjaJVEf2ZDEzpRt24Olr +yBkiT9Zn2HeEgIXhduWh2OZmu5gJPVW6ZXzlzYJcdlHdyaaQJNQVXLZOMSihDn3A +GWKBl8RLmcyZFquW3UgC0KTsTiPhqmDkmC+0fnNSFLsizl8s2kVBvEa56MG2twCj +GewOCIbskx8MWlPbGgKEnZTwtD27/rfgP5V2EtjAGNIl84OZLln7/xrlyOZpoJha +R69OKXeniGUsYYvBNKcmHB+ApwHFUwDWMHAbw0vQicaolUwxLLE1L/Fa3OA2um6+ +ey6gCyLUNy6XoILHJ0ZqNluQWGofAsXNKc1vB52SMu0RNJU= +-----END CERTIFICATE----- diff --git a/integration/microservices/src/tcp-tls/privkey.pem b/integration/microservices/src/tcp-tls/privkey.pem new file mode 100644 index 00000000000..70c5ac74212 --- /dev/null +++ b/integration/microservices/src/tcp-tls/privkey.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpgIBAAKCAQEAzHLaex+5C8Kf+Nh/9F1sHe3LZGVb/8OLTlPAmQqyizHmJ+E7 +1Ibs4iUVMy6fM3MGllUWhoDzd2JjRaXsK7qF85vuksdAyzYbXdfHhOC2djdRtcis +Y6ZlwgDUiPiNRcYG+8jeAnGuLeyix0a+qMU/spr+7cQ50ZmlVTz56w93OZtkl97E +tqWXnJioL5KR6teFJKVHc4jFQvtjOEDG7wb+WFxNoMDn66z/J2kUGSnDRxA1KBu0 +N4vLUIQH9s23gXEg7I4Eb98XHTmrLeRQfasVMpoIu0K7otiqccE1ol7LHqtq09PT +UKdrGWBUZntrXQBiFeQQGE6/Fl6qqZ2iyw4THQIDAQABAoIBAQCDUB2NyTFEIWw4 +G24LmNlxW6MwR33Qh1r5IwfoBBvrFVCbOqn/9e09zs2QAxjTpcSMV/hQyZAWlMXo +HQrUh0ez0OppsbqnCoiHPKl0ahQnV56OoGZA1sYy1e+sTeAG+YrQuxIFBdj2vC9M +yN56bb5cy/qC4m60dffgCHsIg/VytHpuzNkGc5BwPvOlIy16kM3uEsnP/1dz9ocK +gFws+mRekY4Qss95ZIgNI7OLA9Uqp4WDyxenugOIC/Dj+PHMh1Rj41gF/Rh0lzKA +rikPrcsIwKJl5MvqnJKN658tZaqYDyd6OlX8FnbxVb2CZdEmxYqJ14HlBiZXws3b +5ps2nFXtAoGBAPOypjwVtnPWSZy2fBeMVDEDX3JJJStasmFS0XTVDhn1LXrQtpwA +UvINw4VDETTrF8/sP8qAX4+zIhRHgdpC1yvAdu35DOz7WuuZROejDNkyQzYYeDXz +0ssEELiJk//NE+aqI0i8XnzfsSsPLGs7ofVC3vAtZN5htJ/Q6aJgQjWXAoGBANbE ++srpfHAuXAMGXlF2+m5X2KKaYLvj9IBMsd6R8BPZjYmB7TyfWQuDzKjbGVPOnROd +UmIUuX+p0SUybSnPqC1otwO5ZCxMoIgBjitZmay/eqS9WVkBJ390gxf+TQkQAfAb +G043D44RUkmnFMndffgnxOvIDXdPJXr1rIIY81trAoGBAKoMuEjnEcik9/rdr5uy +9TC6XMjf14r4H88fvj7bSJq+Zfz0VOhopXh4OD3uPbyCa7xP63d/uq8IXbhu6WKH +D6RdCeAm0GkxQBF0gwtr0FRi6Vl/G6ryN94oOQ68GcT+smtbv4FKyFg0s2kLjoBJ +PUtqXSyVUHSJE0QNwGno52qDAoGBAM0MtlGDCK7mZCzzKJvo14MAVjIw6IibRLY2 +A2B3v1qETFDr/n/xt4d5562TuiO80VWHz78bAFw12xwDgBv6ShxIrOteVpjA9EfG +DGGxIzq+ei7NJYAHzRkwOXVv80bixKbkinZrtWszeHgfkIaG9R03gxiGIO03YJZ7 +9p8jiT1PAoGBAK8jPabduEg0mHZu5aytDCcOFw+5CRwbMCnnfClzs9VVPZ3lu4wq +JFWI8WQ48harBN6bH0+c6dAJvsCTJg97XN7dmfS3hZTeMDS+vFcX/Q8ALWItyWZo +lF+e2dnbmD9Njim87SdipwI4M6HyYYBa4gKYOc1Y78ejCoK6OG17HLMX +-----END RSA PRIVATE KEY----- From 45899843e004715157b703661608d174f51d8fb7 Mon Sep 17 00:00:00 2001 From: Kamil Mysliwiec Date: Tue, 1 Mar 2022 14:23:34 +0100 Subject: [PATCH 9/9] Update integration/microservices/e2e/sum-rpc-tls.spec.ts --- integration/microservices/e2e/sum-rpc-tls.spec.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/integration/microservices/e2e/sum-rpc-tls.spec.ts b/integration/microservices/e2e/sum-rpc-tls.spec.ts index e7773d2c237..fd8e6e85eb9 100644 --- a/integration/microservices/e2e/sum-rpc-tls.spec.ts +++ b/integration/microservices/e2e/sum-rpc-tls.spec.ts @@ -16,7 +16,6 @@ describe('RPC TLS transport', () => { before(() => { // Generate a self-signed key pair - console.log(__dirname); key = fs .readFileSync(path.join(__dirname, '../src/tcp-tls/privkey.pem'), 'utf8') .toString();