Skip to content

Commit

Permalink
[FABN-1159] Allow PEM paths relative to CCP
Browse files Browse the repository at this point in the history
Allow a connection profile to specify paths to PEM files (for peer,
orderer, and certificate authority  TLS CA certs) that are relative
to the connection profile file. Currently they must be relative to
the current working directory, which is kind of useless if you want
to zip up a connection profile and the PEM files and ship them around.

Change-Id: Icd23c90553e5e4c81f6fe9245b6740b83749ec3e
Signed-off-by: Simon Stone <[email protected]>
  • Loading branch information
Simon Stone committed Feb 22, 2019
1 parent 3ef429f commit 2ee102a
Show file tree
Hide file tree
Showing 11 changed files with 628 additions and 62 deletions.
54 changes: 25 additions & 29 deletions fabric-client/lib/Client.js
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ const Client = class extends BaseClient {
return chaincode;
}

/**
/**
* Returns a {@link TokenClient} instance for the specific channel and targets.
* The TokenClient provides APIs for applications to perform token functions.
*
Expand Down Expand Up @@ -1895,11 +1895,12 @@ function _stringToSignature(string_signatures) {
function _getNetworkConfig(loadConfig, client) {
let network_config = null;
let network_data = null;
let network_config_loc = null;
if (typeof loadConfig === 'string') {
const config_loc = path.resolve(loadConfig);
logger.debug('%s - looking at absolute path of ==>%s<==', '_getNetworkConfig', config_loc);
const file_data = fs.readFileSync(config_loc);
const file_ext = path.extname(config_loc);
network_config_loc = path.resolve(loadConfig);
logger.debug('%s - looking at absolute path of ==>%s<==', '_getNetworkConfig', network_config_loc);
const file_data = fs.readFileSync(network_config_loc);
const file_ext = path.extname(network_config_loc);
// maybe the file is yaml else has to be JSON
if ((/(yml|yaml)$/i).test(file_ext)) {
network_data = yaml.safeLoad(file_data);
Expand All @@ -1910,31 +1911,26 @@ function _getNetworkConfig(loadConfig, client) {
network_data = loadConfig;
}

let error_msg = null;
if (network_data) {
if (network_data.version) {
const parsing = Client.getConfigSetting('network-config-schema');
if (parsing) {
const pieces = network_data.version.toString().split('.');
const version = pieces[0] + '.' + pieces[1];
if (parsing[version]) {
const NetworkConfig = require(parsing[version]);
network_config = new NetworkConfig(network_data, client);
} else {
error_msg = 'common connection profile has an unknown "version"';
}
} else {
error_msg = 'missing "network-config-schema" configuration setting';
}
} else {
error_msg = '"version" is missing';
try {
if (!network_data) {
throw new Error('missing configuration data');
}
} else {
error_msg = 'missing configuration data';
}

if (error_msg) {
throw new Error(util.format('Invalid common connection profile due to %s', error_msg));
if (!network_data.version) {
throw new Error('"version" is missing');
}
const parsing = Client.getConfigSetting('network-config-schema');
if (!parsing) {
throw new Error('missing "network-config-schema" configuration setting');
}
const pieces = network_data.version.toString().split('.');
const version = pieces[0] + '.' + pieces[1];
if (!parsing[version]) {
throw new Error('common connection profile has an unknown "version"');
}
const NetworkConfig = require(parsing[version]);
network_config = new NetworkConfig(network_data, client, network_config_loc);
} catch (error) {
throw new Error(util.format('Invalid common connection profile due to %s', error.message));
}

return network_config;
Expand Down
51 changes: 35 additions & 16 deletions fabric-client/lib/impl/NetworkConfig_1_0.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,25 @@ const NetworkConfig_1_0 = class {
*
* @param {Object} network_config - Common Connection Profile as represented in a JSON object
*/
constructor(network_config, client_context) {
constructor(network_config, client_context, network_config_loc) {
logger.debug('constructor, network_config: ' + JSON.stringify(network_config));
this._network_config = network_config;
this._client_context = client_context;
this._network_config_loc = network_config_loc;
this._peers = new Map();
this._channel = new Map();
this._orderers = new Map();
}

/**
* Get the file system path that the network config was loaded from, if any.
* @returns {string} The file system path that the network config was loaded
* from, or null if it was not loaded directly from the file system.
*/
getNetworkConfigLocation() {
return this._network_config_loc;
}

mergeSettings(additions) {
const method = 'mergeSettings';
logger.debug('%s - additions start', method);
Expand Down Expand Up @@ -163,7 +173,7 @@ const NetworkConfig_1_0 = class {
const peer_config = this._network_config[PEERS_CONFIG][name];
if (peer_config) {
const opts = {name: name};
opts.pem = getTLSCACert(peer_config);
opts.pem = getTLSCACert(peer_config, this._network_config_loc);
Object.assign(opts, peer_config[GRPC_CONNECTION_OPTIONS]);
this.addTimeout(opts, ENDORSER);
peer = this._client_context.newPeer(peer_config[URL], opts);
Expand Down Expand Up @@ -212,7 +222,7 @@ const NetworkConfig_1_0 = class {
const orderer_config = this._network_config[ORDERERS_CONFIG][name];
if (orderer_config) {
const opts = {name: name};
opts.pem = getTLSCACert(orderer_config);
opts.pem = getTLSCACert(orderer_config, this._network_config_loc);
Object.assign(opts, orderer_config[GRPC_CONNECTION_OPTIONS]);
this.addTimeout(opts, ORDERER);
orderer = this._client_context.newOrderer(orderer_config[URL], opts);
Expand Down Expand Up @@ -300,7 +310,7 @@ const NetworkConfig_1_0 = class {
certificateAuthority_config[CANAME],
certificateAuthority_config[URL],
certificateAuthority_config[HTTP_CONNECTION_OPTIONS],
getTLSCACert(certificateAuthority_config),
getTLSCACert(certificateAuthority_config, this._network_config_loc),
certificateAuthority_config[REGISTRAR]
);
}
Expand Down Expand Up @@ -396,38 +406,47 @@ const NetworkConfig_1_0 = class {
}
};

function getTLSCACert(config) {
function getTLSCACert(config, network_config_loc) {
if (config && config[TLS_CACERTS]) {
return getPEMfromConfig(config[TLS_CACERTS]);
return getPEMfromConfig(config[TLS_CACERTS], network_config_loc);
}
return null;
}

function getPEMfromConfig(config) {
function getPEMfromConfig(config, network_config_loc) {
let result = null;
if (config) {
if (config[PEM]) {
// cert value is directly in the configuration
result = config[PEM];
} else if (config[PATH]) {
// cert value is in a file
result = readFileSync(config[PATH]);
result = readFileSync(config[PATH], network_config_loc);
result = utils.normalizeX509(result);
}
}

return result;
}

function readFileSync(config_path) {
try {
const config_loc = path.resolve(config_path);
const data = fs.readFileSync(config_loc);
return Buffer.from(data).toString();
} catch (err) {
logger.error('NetworkConfig101 - problem reading the PEM file :: ' + err);
throw err;
function readFileSync(config_path, network_config_loc) {
const possiblePaths = [
path.resolve(config_path) // relative to cwd
];
if (network_config_loc) {
possiblePaths.push(path.resolve(path.dirname(network_config_loc), config_path));
}
let lastError;
for (const possiblePath of possiblePaths) {
try {
const data = fs.readFileSync(possiblePath);
return Buffer.from(data).toString();
} catch (error) {
lastError = error;
}
}
logger.error('NetworkConfig101 - problem reading the PEM file :: ' + lastError);
throw lastError;
}

module.exports = NetworkConfig_1_0;
30 changes: 15 additions & 15 deletions fabric-client/test/Client.js
Original file line number Diff line number Diff line change
Expand Up @@ -3103,7 +3103,7 @@ describe('Client', () => {
MockNetworkConfig = sinon.stub();
readFileSyncStub = sinon.stub();
revert.push(Client.__set__('fs.readFileSync', readFileSyncStub));
revert.push(Client.__set__('path.resolve', (v) => v));
revert.push(Client.__set__('path.resolve', (v) => `/path/to/${v}`));
safeLoadStub = sinon.stub();
revert.push(Client.__set__('yaml.safeLoad', safeLoadStub));
});
Expand Down Expand Up @@ -3136,19 +3136,19 @@ describe('Client', () => {
sinon.assert.calledWith(getConfigSettingStub, 'network-config-schema');
});

it('should return the new network config when config is an object', () => {
it('should return the new network config when config is a 1.0 object', () => {
readFileSyncStub.returns('file-data');
getConfigSettingStub.returns({'1.0': 'network-config-file'});
requireStub.returns(MockNetworkConfig);
MockNetworkConfig.returns('network-config');
const networkConfig = _getNetworkConfig({version: '1.0'}, 'client');
sinon.assert.calledWith(getConfigSettingStub, 'network-config-schema');
sinon.assert.calledWith(requireStub, 'network-config-file');
sinon.assert.calledWith(MockNetworkConfig, {version: '1.0'}, 'client');
sinon.assert.calledWithExactly(MockNetworkConfig, {version: '1.0'}, 'client', null);
networkConfig.should.deep.equal(new MockNetworkConfig());
});

it('should return the new network config when config is a yaml file path', () => {
it('should return the new network config when config is a 1.0 yaml file path', () => {
readFileSyncStub.returns('file-data');
safeLoadStub.returns({version: '1.0'});
getConfigSettingStub.returns({'1.0': 'network-config-file'});
Expand All @@ -3157,13 +3157,13 @@ describe('Client', () => {
const networkConfig = _getNetworkConfig('config.yaml', 'client');
sinon.assert.calledWith(getConfigSettingStub, 'network-config-schema');
sinon.assert.calledWith(requireStub, 'network-config-file');
sinon.assert.calledWith(MockNetworkConfig, {version: '1.0'}, 'client');
sinon.assert.calledWith(readFileSyncStub, 'config.yaml');
sinon.assert.calledWithExactly(MockNetworkConfig, {version: '1.0'}, 'client', '/path/to/config.yaml');
sinon.assert.calledWith(readFileSyncStub, '/path/to/config.yaml');
sinon.assert.calledWith(safeLoadStub, 'file-data');
networkConfig.should.deep.equal(new MockNetworkConfig());
});

it('should return the new network config when config is a yml file path', () => {
it('should return the new network config when config is a 1.0 yml file path', () => {
readFileSyncStub.returns('file-data');
safeLoadStub.returns({version: '1.0'});
getConfigSettingStub.returns({'1.0': 'network-config-file'});
Expand All @@ -3172,13 +3172,13 @@ describe('Client', () => {
const networkConfig = _getNetworkConfig('config.yml', 'client');
sinon.assert.calledWith(getConfigSettingStub, 'network-config-schema');
sinon.assert.calledWith(requireStub, 'network-config-file');
sinon.assert.calledWith(MockNetworkConfig, {version: '1.0'}, 'client');
sinon.assert.calledWith(readFileSyncStub, 'config.yml');
sinon.assert.calledWithExactly(MockNetworkConfig, {version: '1.0'}, 'client', '/path/to/config.yml');
sinon.assert.calledWith(readFileSyncStub, '/path/to/config.yml');
sinon.assert.calledWith(safeLoadStub, 'file-data');
networkConfig.should.deep.equal(new MockNetworkConfig());
});

it('should return the new network config when config is a yaml file path', () => {
it('should return the new network config when config is a 1.0 json file path', () => {
readFileSyncStub.returns('{"version": "1.0"}');
safeLoadStub.returns({version: '1.0'});
getConfigSettingStub.returns({'1.0': 'network-config-file'});
Expand All @@ -3187,12 +3187,12 @@ describe('Client', () => {
const networkConfig = _getNetworkConfig('config.json', 'client');
sinon.assert.calledWith(getConfigSettingStub, 'network-config-schema');
sinon.assert.calledWith(requireStub, 'network-config-file');
sinon.assert.calledWith(MockNetworkConfig, {version: '1.0'}, 'client');
sinon.assert.calledWith(readFileSyncStub, 'config.json');
sinon.assert.calledWithExactly(MockNetworkConfig, {version: '1.0'}, 'client', '/path/to/config.json');
sinon.assert.calledWith(readFileSyncStub, '/path/to/config.json');
networkConfig.should.deep.equal(new MockNetworkConfig());
});

it('should return the new network config when config is a yaml file path', () => {
it('should return the new network config when config is a 1.1 json file path', () => {
readFileSyncStub.returns('{"version": 1.1}');
safeLoadStub.returns({version: 1.1});
getConfigSettingStub.returns({'1.1': 'network-config-file'});
Expand All @@ -3201,8 +3201,8 @@ describe('Client', () => {
const networkConfig = _getNetworkConfig('config.json', 'client');
sinon.assert.calledWith(getConfigSettingStub, 'network-config-schema');
sinon.assert.calledWith(requireStub, 'network-config-file');
sinon.assert.calledWith(MockNetworkConfig, {version: 1.1}, 'client');
sinon.assert.calledWith(readFileSyncStub, 'config.json');
sinon.assert.calledWithExactly(MockNetworkConfig, {version: 1.1}, 'client', '/path/to/config.json');
sinon.assert.calledWith(readFileSyncStub, '/path/to/config.json');
networkConfig.should.deep.equal(new MockNetworkConfig());
});
});
Expand Down
51 changes: 51 additions & 0 deletions fabric-client/test/data/ccp/connection-embedded-pems.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
{
"name": "basic-network",
"version": "1.0.0",
"client": {
"organization": "Org1",
"connection": {
"timeout": {
"peer": {
"endorser": "300"
},
"orderer": "300"
}
}
},
"organizations": {
"Org1": {
"mspid": "Org1MSP",
"peers": [
"peer0.org1.example.com"
],
"certificateAuthorities": [
"ca.example.com"
]
}
},
"orderers": {
"orderer.example.com": {
"url": "grpcs://localhost:7050",
"tlsCACerts": {
"pem": "-----BEGIN CERTIFICATE-----\nMIICSTCCAe+gAwIBAgIQPHXmPqjzn2bon7JrBRPS2DAKBggqhkjOPQQDAjB2MQsw\nCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy\nYW5jaXNjbzEZMBcGA1UEChMQb3JnMS5leGFtcGxlLmNvbTEfMB0GA1UEAxMWdGxz\nY2Eub3JnMS5leGFtcGxlLmNvbTAeFw0xOTAyMjExNDI4MDBaFw0yOTAyMTgxNDI4\nMDBaMHYxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQH\nEw1TYW4gRnJhbmNpc2NvMRkwFwYDVQQKExBvcmcxLmV4YW1wbGUuY29tMR8wHQYD\nVQQDExZ0bHNjYS5vcmcxLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0D\nAQcDQgAELAsSPvzK3EdhGPZAMKYh67s02WqfYUe09xMzy7BzNODUKcbyIW5i7GVQ\n3YurSkR/auRsk6FG45Q1zTZaEvwVH6NfMF0wDgYDVR0PAQH/BAQDAgGmMA8GA1Ud\nJQQIMAYGBFUdJQAwDwYDVR0TAQH/BAUwAwEB/zApBgNVHQ4EIgQg8HHn3ScArMdH\nlkp+jpcDXtIAzWnVf4F9rBHvUNjcC1owCgYIKoZIzj0EAwIDSAAwRQIhAMi+R+ZI\nXgZV40IztD8aQDr/sntDTu/8Nw7Y0DGEhwaQAiBEnBCdRXaBcENWnAnastAg+RA5\nXALSidlQqZKrK4L3Yg==\n-----END CERTIFICATE-----\n"
}
}
},
"peers": {
"peer0.org1.example.com": {
"url": "grpcs://localhost:7051",
"tlsCACerts": {
"pem": "-----BEGIN CERTIFICATE-----\nMIICSTCCAe+gAwIBAgIQPHXmPqjzn2bon7JrBRPS2DAKBggqhkjOPQQDAjB2MQsw\nCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy\nYW5jaXNjbzEZMBcGA1UEChMQb3JnMS5leGFtcGxlLmNvbTEfMB0GA1UEAxMWdGxz\nY2Eub3JnMS5leGFtcGxlLmNvbTAeFw0xOTAyMjExNDI4MDBaFw0yOTAyMTgxNDI4\nMDBaMHYxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQH\nEw1TYW4gRnJhbmNpc2NvMRkwFwYDVQQKExBvcmcxLmV4YW1wbGUuY29tMR8wHQYD\nVQQDExZ0bHNjYS5vcmcxLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0D\nAQcDQgAELAsSPvzK3EdhGPZAMKYh67s02WqfYUe09xMzy7BzNODUKcbyIW5i7GVQ\n3YurSkR/auRsk6FG45Q1zTZaEvwVH6NfMF0wDgYDVR0PAQH/BAQDAgGmMA8GA1Ud\nJQQIMAYGBFUdJQAwDwYDVR0TAQH/BAUwAwEB/zApBgNVHQ4EIgQg8HHn3ScArMdH\nlkp+jpcDXtIAzWnVf4F9rBHvUNjcC1owCgYIKoZIzj0EAwIDSAAwRQIhAMi+R+ZI\nXgZV40IztD8aQDr/sntDTu/8Nw7Y0DGEhwaQAiBEnBCdRXaBcENWnAnastAg+RA5\nXALSidlQqZKrK4L3Yg==\n-----END CERTIFICATE-----\n"
}
}
},
"certificateAuthorities": {
"ca.example.com": {
"url": "https://localhost:7054",
"caName": "ca.example.com",
"tlsCACerts": {
"pem": "-----BEGIN CERTIFICATE-----\nMIICSTCCAe+gAwIBAgIQPHXmPqjzn2bon7JrBRPS2DAKBggqhkjOPQQDAjB2MQsw\nCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy\nYW5jaXNjbzEZMBcGA1UEChMQb3JnMS5leGFtcGxlLmNvbTEfMB0GA1UEAxMWdGxz\nY2Eub3JnMS5leGFtcGxlLmNvbTAeFw0xOTAyMjExNDI4MDBaFw0yOTAyMTgxNDI4\nMDBaMHYxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQH\nEw1TYW4gRnJhbmNpc2NvMRkwFwYDVQQKExBvcmcxLmV4YW1wbGUuY29tMR8wHQYD\nVQQDExZ0bHNjYS5vcmcxLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0D\nAQcDQgAELAsSPvzK3EdhGPZAMKYh67s02WqfYUe09xMzy7BzNODUKcbyIW5i7GVQ\n3YurSkR/auRsk6FG45Q1zTZaEvwVH6NfMF0wDgYDVR0PAQH/BAQDAgGmMA8GA1Ud\nJQQIMAYGBFUdJQAwDwYDVR0TAQH/BAUwAwEB/zApBgNVHQ4EIgQg8HHn3ScArMdH\nlkp+jpcDXtIAzWnVf4F9rBHvUNjcC1owCgYIKoZIzj0EAwIDSAAwRQIhAMi+R+ZI\nXgZV40IztD8aQDr/sntDTu/8Nw7Y0DGEhwaQAiBEnBCdRXaBcENWnAnastAg+RA5\nXALSidlQqZKrK4L3Yg==\n-----END CERTIFICATE-----\n"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
{
"name": "basic-network",
"version": "1.0.0",
"client": {
"organization": "Org1",
"connection": {
"timeout": {
"peer": {
"endorser": "300"
},
"orderer": "300"
}
}
},
"organizations": {
"Org1": {
"mspid": "Org1MSP",
"peers": [
"peer0.org1.example.com"
],
"certificateAuthorities": [
"ca.example.com"
]
}
},
"orderers": {
"orderer.example.com": {
"url": "grpcs://localhost:7050",
"tlsCACerts": {
"path": "pems/notherelulz.pem"
}
}
},
"peers": {
"peer0.org1.example.com": {
"url": "grpcs://localhost:7051",
"tlsCACerts": {
"path": "pems/notherelulz.pem"
}
}
},
"certificateAuthorities": {
"ca.example.com": {
"url": "https://localhost:7054",
"caName": "ca.example.com",
"tlsCACerts": {
"path": "pems/notherelulz.pem"
}
}
}
}
Loading

0 comments on commit 2ee102a

Please sign in to comment.