Skip to content

Commit

Permalink
Allow reverseProxy to terminate SSL
Browse files Browse the repository at this point in the history
  • Loading branch information
sisou committed Jul 12, 2024
1 parent a4bc81e commit eb5b3bb
Show file tree
Hide file tree
Showing 17 changed files with 131 additions and 50 deletions.
4 changes: 2 additions & 2 deletions clients/nodejs/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ const $ = {};

const clientConfigBuilder = Nimiq.Client.Configuration.builder();
clientConfigBuilder.protocol(config.protocol, config.host, config.port, config.tls.key, config.tls.cert);
if (config.reverseProxy.enabled) clientConfigBuilder.reverseProxy(config.reverseProxy.port, config.reverseProxy.header, ...config.reverseProxy.addresses);
if (config.reverseProxy.enabled) clientConfigBuilder.reverseProxy(config.reverseProxy.port, config.reverseProxy.header, config.reverseProxy.terminatesSsl, ...config.reverseProxy.addresses);
if (config.passive) clientConfigBuilder.feature(Nimiq.Client.Feature.PASSIVE);
if (config.type === 'full' || config.type === 'light') clientConfigBuilder.feature(Nimiq.Client.Feature.MEMPOOL);
const clientConfig = clientConfigBuilder.build();
Expand Down Expand Up @@ -200,7 +200,7 @@ const $ = {};
$.mempool = $.consensus.mempool;
$.network = $.consensus.network;

Nimiq.Log.i(TAG, `Peer address: ${networkConfig.peerAddress.toString()} - public key: ${networkConfig.keyPair.publicKey.toHex()}`);
Nimiq.Log.i(TAG, `Peer address: ${networkConfig.publicPeerAddress.toString()} - public key: ${networkConfig.keyPair.publicKey.toHex()}`);

// TODO: Wallet key.
$.walletStore = await new Nimiq.WalletStore();
Expand Down
8 changes: 5 additions & 3 deletions clients/nodejs/modules/Config.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const TAG = 'Config';
* @property {{enabled: boolean, port: number}} uiServer
* @property {{enabled: boolean, port: number, password: string}} metricsServer
* @property {{seed: string, address: string}} wallet
* @property {{enabled: boolean, port: number, address: string, addresses: Array.<string>, header: string}} reverseProxy
* @property {{enabled: boolean, port: number, address: string, addresses: Array.<string>, header: string, terminatesSsl: boolean}} reverseProxy
* @property {{level: string, tags: object}} log
* @property {Array.<{host: string, port: number, publicKey: string, protocol: string}>} seedPeers
* @property {object} constantOverrides
Expand Down Expand Up @@ -83,7 +83,8 @@ const DEFAULT_CONFIG = /** @type {Config} */ {
port: 8444,
address: '::ffff:127.0.0.1', // deprecated
addresses: [],
header: 'x-forwarded-for'
header: 'x-forwarded-for',
terminatesSsl: false
},
log: {
level: 'info',
Expand Down Expand Up @@ -163,7 +164,8 @@ const CONFIG_TYPES = {
port: 'number',
address: 'string', // deprecated
addresses: {type: 'array', inner: 'string'},
header: 'string'
header: 'string',
terminatesSsl: 'boolean'
}
},
log: {
Expand Down
2 changes: 1 addition & 1 deletion clients/nodejs/modules/MetricsServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ class MetricsServer {

get _desc() {
return {
peer: this._network._networkConfig.peerAddress.toString()
peer: this._network._networkConfig.internalPeerAddress.toString()
};
}

Expand Down
4 changes: 4 additions & 0 deletions clients/nodejs/sample.conf
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,10 @@
// Possible values: any valid HTTP header name
// Default: "x-forwarded-for"
//header: "x-forwarded-for"

// Set termination of SSL on the reverse proxy.
// Default: false
//terminatesSsl: true,
},

// Configure log output. All output will go to STDOUT.
Expand Down
8 changes: 4 additions & 4 deletions dist/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ declare class ClientConfigurationBuilder {
public volatile(volatile?: boolean): this;
public blockConfirmations(confirmations: number): this;
public feature(...feature: Client.Feature[]): this;
public reverseProxy(port: number, header: string, ...addresses: string[]): this;
public reverseProxy(port: number, header: string, terminatesSsl: boolean, ...addresses: string[]): this;
public build(): Client.Configuration;
public instantiateClient(): Client;
}
Expand Down Expand Up @@ -4014,13 +4014,13 @@ export class NetworkConfig {
export class WsNetworkConfig extends NetworkConfig {
public protocol: number;
public port: number;
public reverseProxy: { enabled: boolean, port: number, addresses: string[], header: string };
public reverseProxy: { enabled: boolean, port: number, addresses: string[], header: string, terminatesSsl: boolean };
public peerAddress: WsPeerAddress | WssPeerAddress;
public secure: boolean;
constructor(
host: string,
port: number,
reverseProxy: { enabled: boolean, port: number, addresses: string[], header: string },
reverseProxy: { enabled: boolean, port: number, addresses: string[], header: string, terminatesSsl: boolean },
);
}

Expand All @@ -4031,7 +4031,7 @@ export class WssNetworkConfig extends WsNetworkConfig {
port: number,
key: string,
cert: string,
reverseProxy: { enabled: boolean, port: number, addresses: string[], header: string },
reverseProxy: { enabled: boolean, port: number, addresses: string[], header: string, terminatesSsl: boolean },
);
}

Expand Down
4 changes: 3 additions & 1 deletion src/main/generic/api/Configuration.js
Original file line number Diff line number Diff line change
Expand Up @@ -184,15 +184,17 @@ Client.ConfigurationBuilder = class ConfigurationBuilder {
/**
* @param {number} port
* @param {string} header
* @param {boolean} terminatesSsl
* @param {...string} addresses
* @returns {Client.ConfigurationBuilder}
*/
reverseProxy(port, header, ...addresses) {
reverseProxy(port, header, terminatesSsl, ...addresses) {
if (this._protocol !== 'ws' && this._protocol !== 'wss') throw new Error('Protocol must be ws or wss for reverse proxy.');
this._reverseProxy = {
enabled: true,
port: this._requiredType(port, 'port', 'number'),
header: this._requiredType(header, 'header', 'string'),
terminatesSsl,
addresses: addresses
};
return this;
Expand Down
2 changes: 1 addition & 1 deletion src/main/generic/api/NetworkClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ Client.Network = class Network {
*/
async getOwnAddress() {
const consensus = await this._client._consensus;
return new Client.BasicAddress(consensus.network.config.peerAddress);
return new Client.BasicAddress(consensus.network.config.publicPeerAddress);
}

/**
Expand Down
89 changes: 81 additions & 8 deletions src/main/generic/network/NetworkConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,14 @@ class NetworkConfig {
/**
* @type {PeerAddress}
*/
get peerAddress() {
get internalPeerAddress() {
throw new Error('Not implemented');
}

/**
* @type {PeerAddress}
*/
get publicPeerAddress() {
throw new Error('Not implemented');
}

Expand Down Expand Up @@ -157,7 +164,7 @@ class WsNetworkConfig extends NetworkConfig {
* @constructor
* @param {string} host
* @param {number} port
* @param {{enabled: boolean, port: number, addresses: Array.<string>, header: string}} reverseProxy
* @param {{enabled: boolean, port: number, addresses: Array.<string>, header: string, terminatesSsl: boolean}} reverseProxy
*/
constructor(host, port, reverseProxy) {
super(Protocol.WS | Protocol.WSS);
Expand All @@ -182,7 +189,7 @@ class WsNetworkConfig extends NetworkConfig {
}

/**
* @type {{enabled: boolean, port: number, addresses: Array.<string>, header: string}}
* @type {{enabled: boolean, port: number, addresses: Array.<string>, header: string, terminatesSsl: boolean}}
*/
get reverseProxy() {
return this._reverseProxy;
Expand All @@ -192,13 +199,41 @@ class WsNetworkConfig extends NetworkConfig {
* @type {WsPeerAddress|WssPeerAddress}
* @override
*/
get peerAddress() {
get internalPeerAddress() {
if (!this._services || !this._keyPair) {
throw new Error('PeerAddress is not configured.');
}

const port = this._reverseProxy.enabled ? this._reverseProxy.port : this._port;
const peerAddress = new WsPeerAddress(
this._services.provided, Date.now(), NetAddress.UNSPECIFIED,
this.publicKey, /*distance*/ 0,
this._host, this._port);

if (!peerAddress.globallyReachable()) {
throw new Error('PeerAddress not globally reachable.');
}

peerAddress.signature = Signature.create(this._keyPair.privateKey, this.publicKey, peerAddress.serializeContent());
return peerAddress;
}

/**
* @type {WsPeerAddress|WssPeerAddress}
* @override
*/
get publicPeerAddress() {
if (!this._services || !this._keyPair) {
throw new Error('PeerAddress is not configured.');
}

const port = this._reverseProxy.enabled ? this._reverseProxy.port : this._port;
let _PeerAddress;
if (this._reverseProxy.enabled && this._reverseProxy.terminatesSsl) {
_PeerAddress = WssPeerAddress;
} else {
_PeerAddress = WsPeerAddress;
}
const peerAddress = new _PeerAddress(
this._services.provided, Date.now(), NetAddress.UNSPECIFIED,
this.publicKey, /*distance*/ 0,
this._host, port);
Expand Down Expand Up @@ -260,7 +295,29 @@ class WssNetworkConfig extends WsNetworkConfig {
* @type {WsPeerAddress|WssPeerAddress}
* @override
*/
get peerAddress() {
get internalPeerAddress() {
if (!this._services || !this._keyPair) {
throw new Error('PeerAddress is not configured.');
}

const peerAddress = new WssPeerAddress(
this._services.provided, Date.now(), NetAddress.UNSPECIFIED,
this.publicKey, /*distance*/ 0,
this._host, this._port);

if (!peerAddress.globallyReachable()) {
throw new Error('PeerAddress not globally reachable.');
}

peerAddress.signature = Signature.create(this._keyPair.privateKey, this.publicKey, peerAddress.serializeContent());
return peerAddress;
}

/**
* @type {WsPeerAddress|WssPeerAddress}
* @override
*/
get publicPeerAddress() {
if (!this._services || !this._keyPair) {
throw new Error('PeerAddress is not configured.');
}
Expand Down Expand Up @@ -321,7 +378,7 @@ class RtcNetworkConfig extends NetworkConfig {
* @type {RtcPeerAddress}
* @override
*/
get peerAddress() {
get internalPeerAddress() {
if (!this._services || !this._keyPair) {
throw new Error('PeerAddress is not configured.');
}
Expand All @@ -332,6 +389,14 @@ class RtcNetworkConfig extends NetworkConfig {
peerAddress.signature = Signature.create(this._keyPair.privateKey, this.publicKey, peerAddress.serializeContent());
return peerAddress;
}

/**
* @type {RtcPeerAddress}
* @override
*/
get publicPeerAddress() {
return this.internalPeerAddress;
}
}
Class.register(RtcNetworkConfig);

Expand All @@ -356,7 +421,7 @@ class DumbNetworkConfig extends NetworkConfig {
* @type {DumbPeerAddress}
* @override
*/
get peerAddress() {
get internalPeerAddress() {
if (!this._services || !this._keyPair) {
throw new Error('PeerAddress is not configured.');
}
Expand All @@ -367,5 +432,13 @@ class DumbNetworkConfig extends NetworkConfig {
peerAddress.signature = Signature.create(this._keyPair.privateKey, this.publicKey, peerAddress.serializeContent());
return peerAddress;
}

/**
* @type {DumbPeerAddress}
* @override
*/
get publicPeerAddress() {
return this.internalPeerAddress;
}
}
Class.register(DumbNetworkConfig);
2 changes: 1 addition & 1 deletion src/main/generic/network/address/PeerAddressBook.js
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ class PeerAddressBook extends Observable {
*/
_add(channel, peerAddress) {
// Ignore our own address.
if (this._networkConfig.peerAddress.equals(peerAddress)) {
if (this._networkConfig.publicPeerAddress.equals(peerAddress)) {
return false;
}

Expand Down
2 changes: 1 addition & 1 deletion src/main/generic/network/connection/ConnectionPool.js
Original file line number Diff line number Diff line change
Expand Up @@ -546,7 +546,7 @@ class ConnectionPool extends Observable {

case PeerConnectionState.NEGOTIATING:
// The peer with the lower peerId accepts this connection and closes his stored connection.
if (this._networkConfig.peerAddress.peerId.compare(peer.peerAddress.peerId) < 0) {
if (this._networkConfig.publicPeerAddress.peerId.compare(peer.peerAddress.peerId) < 0) {
storedConnection.peerChannel.close(CloseType.SIMULTANEOUS_CONNECTION,
'simultaneous connection (post handshake) - lower peerId');
Assert.that(!this.getConnectionByPeerAddress(peer.peerAddress), 'PeerConnection not removed');
Expand Down
6 changes: 3 additions & 3 deletions src/main/generic/network/connection/NetworkAgent.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ class NetworkAgent extends Observable {
// Kick off the handshake by telling the peer our version, network address & blockchain head hash.
// Some browsers (Firefox, Safari) send the data-channel-open event too early, so sending the version message might fail.
// Try again in this case.
if (!this._channel.version(this._networkConfig.peerAddress, this._blockchain.headHash, this._challengeNonce, this._networkConfig.appAgent)) {
if (!this._channel.version(this._networkConfig.publicPeerAddress, this._blockchain.headHash, this._challengeNonce, this._networkConfig.appAgent)) {
this._versionAttempts++;
if (this._versionAttempts >= NetworkAgent.VERSION_ATTEMPTS_MAX || this._channel.closed) {
this._channel.close(CloseType.SENDING_OF_VERSION_MESSAGE_FAILED, 'sending of version message failed');
Expand Down Expand Up @@ -303,7 +303,7 @@ class NetworkAgent extends Observable {
}

// Verify signature
const data = BufferUtils.concatTypedArrays(this._networkConfig.peerAddress.peerId.serialize(), this._challengeNonce);
const data = BufferUtils.concatTypedArrays(this._networkConfig.publicPeerAddress.peerId.serialize(), this._challengeNonce);
if (!msg.signature.verify(msg.publicKey, data)) {
this._channel.close(CloseType.INVALID_SIGNATURE_IN_VERACK_MESSAGE, 'Invalid signature in verack message');
return;
Expand All @@ -330,7 +330,7 @@ class NetworkAgent extends Observable {

// Regularly announce our address.
this._timers.setInterval('announce-addr',
() => this._channel.addr([this._networkConfig.peerAddress]),
() => this._channel.addr([this._networkConfig.publicPeerAddress]),
NetworkAgent.ANNOUNCE_ADDR_INTERVAL);

// Tell listeners that the handshake with this peer succeeded.
Expand Down
4 changes: 2 additions & 2 deletions src/main/generic/network/websocket/WebSocketConnector.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ class WebSocketConnector extends Observable {
this._protocolPrefix = protocolPrefix;
this._networkConfig = networkConfig;

if (networkConfig.peerAddress.protocol === this._protocol) {
if (networkConfig.internalPeerAddress.protocol === this._protocol) {
this._wss = WebSocketFactory.newWebSocketServer(networkConfig);
this._wss.on('connection', (ws, req) => this._onConnection(ws, req));

Log.d(WebSocketConnector, `${this._protocolPrefix.toUpperCase()}-Connector listening on port ${networkConfig.peerAddress.port}`);
Log.d(WebSocketConnector, `${this._protocolPrefix.toUpperCase()}-Connector listening on port ${networkConfig.internalPeerAddress.port}`);
}

/** @type {HashMap.<PeerAddress, WebSocket>} */
Expand Down
2 changes: 1 addition & 1 deletion src/test/specs/generic/DummyData.spec.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/test/specs/generic/api/Client.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ describe('Client', () => {
const name = 'volatile' + consensus.charAt(0).toUpperCase() + consensus.slice(1);
const promise = Consensus[name]();
promise.then((c) => {
Log.d('Client.spec', `${consensus}-consensus uses ${c.network.config.peerAddress}`);
Log.d('Client.spec', `${consensus}-consensus uses ${c.network.config.publicPeerAddress}`);
});
return promise;
}
Expand Down
4 changes: 2 additions & 2 deletions src/test/specs/generic/consensus/light/LightConsensus.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ describe('LightConsensus', () => {
});
await blockchain2.pushBlock(block);
}
const netConfig2 = new WssNetworkConfig('node2.test', 8080, 'key2', 'cert2', { enabled: false, port: 8444, address: '::ffff:127.0.0.1', header: 'x-forwarded-for'});
const netConfig2 = new WssNetworkConfig('node2.test', 8080, 'key2', 'cert2', { enabled: false, port: 8444, address: '::ffff:127.0.0.1', header: 'x-forwarded-for', terminatesSsl: false});
const consensus2 = await Consensus.volatileFull(netConfig2);
consensus2.network.allowInboundConnections = true;
await copyChain(blockchain2, consensus2.blockchain);
Expand All @@ -61,7 +61,7 @@ describe('LightConsensus', () => {
expect(consensus3.blockchain.height).toBe(9);

// Connect to peer 2.
consensus3.network._connections.connectOutbound(netConfig2.peerAddress);
consensus3.network._connections.connectOutbound(netConfig2.publicPeerAddress);

setTimeout(() => {
expect(consensus1.blockchain.head.equals(blockchain2.head)).toBe(true);
Expand Down
Loading

0 comments on commit eb5b3bb

Please sign in to comment.