Skip to content

Commit

Permalink
Merge pull request #135 from kaleido-io/mtls-support
Browse files Browse the repository at this point in the history
Mtls support
  • Loading branch information
shorsher committed May 9, 2023
2 parents 5d9269a + 74b9e7b commit 54fb773
Show file tree
Hide file tree
Showing 6 changed files with 81 additions and 12 deletions.
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,3 +206,13 @@ The configurable retry settings are:
Setting `RETRY_CONDITION` to `""` disables retries. Setting `RETRY_MAX_ATTEMPTS` to `-1` causes it to retry indefinitely.

Note, the token connector will make a total of `RETRY_MAX_ATTEMPTS` + 1 calls for a given retryable call (1 original attempt and `RETRY_MAX_ATTEMPTS` retries)

## TLS

Mutual TLS can be enabled by providing three environment variables:

- `TLS_CA`
- `TLS_CERT`
- `TLS_KEY`

Each should be a path to a file on disk. Providing all three environment variables will result in a token connector running with TLS enabled, and requiring all clients to provide client certificates signed by the certificate authority.
8 changes: 3 additions & 5 deletions src/event-stream/event-stream.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import WebSocket from 'ws';
import { FFRequestIDHeader } from '../request-context/constants';
import { Context } from '../request-context/request-context.decorator';
import { IAbiMethod } from '../tokens/tokens.interfaces';
import { basicAuth } from '../utils';
import { getHttpRequestOptions, getWebsocketOptions } from '../utils';
import {
Event,
EventBatch,
Expand Down Expand Up @@ -58,9 +58,7 @@ export class EventStreamSocket {
this.disconnectDetected = false;
this.closeRequested = false;

const auth =
this.username && this.password ? { auth: `${this.username}:${this.password}` } : undefined;
this.ws = new WebSocket(this.url, auth);
this.ws = new WebSocket(this.url, getWebsocketOptions(this.username, this.password));
this.ws
.on('open', () => {
if (this.disconnectDetected) {
Expand Down Expand Up @@ -173,7 +171,7 @@ export class EventStreamService {
}
}
headers[FFRequestIDHeader] = ctx.requestId;
const config = basicAuth(this.username, this.password);
const config = getHttpRequestOptions(this.username, this.password);
config.headers = headers;
return config;
}
Expand Down
4 changes: 2 additions & 2 deletions src/health/health.controller.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Controller, Get } from '@nestjs/common';
import { HealthCheckService, HealthCheck, HttpHealthIndicator } from '@nestjs/terminus';
import { BlockchainConnectorService } from '../tokens/blockchain.service';
import { basicAuth } from '../utils';
import { getHttpRequestOptions } from '../utils';

@Controller('health')
export class HealthController {
Expand All @@ -25,7 +25,7 @@ export class HealthController {
this.http.pingCheck(
'ethconnect',
`${this.blockchain.baseUrl}/status`,
basicAuth(this.blockchain.username, this.blockchain.password),
getHttpRequestOptions(this.blockchain.username, this.blockchain.password),
),
]);
}
Expand Down
6 changes: 4 additions & 2 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import { ShutdownSignal, ValidationPipe } from '@nestjs/common';
import { NestApplicationOptions, ShutdownSignal, ValidationPipe } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { NestFactory } from '@nestjs/core';
import { WsAdapter } from '@nestjs/platform-ws';
Expand All @@ -36,6 +36,7 @@ import {
import { TokensService } from './tokens/tokens.service';
import { newContext } from './request-context/request-context.decorator';
import { AbiMapperService } from './tokens/abimapper.service';
import { getNestOptions } from './utils';

const API_DESCRIPTION = `
<p>All POST APIs are asynchronous. Listen for websocket notifications on <code>/api/ws</code>.
Expand All @@ -50,7 +51,8 @@ export function getApiConfig() {
}

async function bootstrap() {
const app = await NestFactory.create(AppModule);
const app = await NestFactory.create(AppModule, getNestOptions());

app.setGlobalPrefix('api/v1');
app.useWebSocketAdapter(new WsAdapter(app));
app.useGlobalPipes(new ValidationPipe({ whitelist: true }));
Expand Down
4 changes: 2 additions & 2 deletions src/tokens/blockchain.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import {
import axios, { AxiosRequestConfig, AxiosResponse } from 'axios';
import { lastValueFrom } from 'rxjs';
import { EventStreamReply } from '../event-stream/event-stream.interfaces';
import { basicAuth } from '../utils';
import { getHttpRequestOptions } from '../utils';
import { Context } from '../request-context/request-context.decorator';
import { FFRequestIDHeader } from '../request-context/constants';
import { EthConnectAsyncResponse, EthConnectReturn, IAbiMethod } from './tokens.interfaces';
Expand Down Expand Up @@ -80,7 +80,7 @@ export class BlockchainConnectorService {
}
}
headers[FFRequestIDHeader] = ctx.requestId;
const config = basicAuth(this.username, this.password);
const config = getHttpRequestOptions(this.username, this.password);
config.headers = headers;
return config;
}
Expand Down
61 changes: 60 additions & 1 deletion src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,71 @@
import * as fs from 'fs';
import * as https from 'https';
import { NestApplicationOptions } from '@nestjs/common';
import { AxiosRequestConfig } from 'axios';
import { ClientOptions } from 'ws';

export const basicAuth = (username: string, password: string) => {
interface Certificates {
key: string;
cert: string;
ca: string;
}

const getCertificates = (): Certificates | undefined => {
let key, cert, ca;
if (
process.env['TLS_KEY'] === undefined ||
process.env['TLS_CERT'] === undefined ||
process.env['TLS_CA'] === undefined
) {
return undefined;
}
try {
key = fs.readFileSync(process.env['TLS_KEY']).toString();
cert = fs.readFileSync(process.env['TLS_CERT']).toString();
ca = fs.readFileSync(process.env['TLS_CA']).toString();
} catch (error) {
console.error(`Error reading certificates: ${error}`);
process.exit(-1);
}
return { key, cert, ca };
};

export const getWebsocketOptions = (username: string, password: string): ClientOptions => {
const requestOptions: ClientOptions = {};
if (username && username !== '' && password && password !== '') {
requestOptions.headers = {
Authorization: `Basic ${Buffer.from(`${username}:${password}`).toString('base64')}`,
};
}
const certs = getCertificates();
if (certs) {
requestOptions.ca = certs.ca;
requestOptions.cert = certs.cert;
requestOptions.key = certs.key;
}
return requestOptions;
};

export const getHttpRequestOptions = (username: string, password: string) => {
const requestOptions: AxiosRequestConfig = {};
if (username !== '' && password !== '') {
requestOptions.auth = {
username: username,
password: password,
};
}
const certs = getCertificates();
if (certs) {
requestOptions.httpsAgent = new https.Agent({ ...certs, requestCert: true });
}
return requestOptions;
};

export const getNestOptions = (): NestApplicationOptions => {
const options: NestApplicationOptions = {};
const certs = getCertificates();
if (certs) {
options.httpsOptions = certs;
}
return options;
};

0 comments on commit 54fb773

Please sign in to comment.