Encrypt credentials by default

This commit is contained in:
Nick O'Leary
2016-09-23 10:38:30 +01:00
parent 44c35d2644
commit d29abc2724
8 changed files with 477 additions and 39 deletions

View File

@@ -92,7 +92,7 @@ module.exports = {
return messageId;
};
redNodes.init({settings:settings, storage:storage});
redNodes.init({settings:settings, storage:storage,log:log});
RED.nodes.registerType("helper", helperNode);
if (Array.isArray(testNode)) {
for (i = 0; i < testNode.length; i++) {

View File

@@ -26,12 +26,21 @@ var log = require("../../../../red/runtime/log");
describe('red/runtime/nodes/credentials', function() {
var encryptionDisabledSettings = {
get: function(key) {
return false;
}
}
afterEach(function() {
index.clearRegistry();
});
it('loads provided credentials',function(done) {
credentials.init();
credentials.init({
log: log,
settings: encryptionDisabledSettings
});
credentials.load({"a":{"b":1,"c":2}}).then(function() {
@@ -42,37 +51,46 @@ describe('red/runtime/nodes/credentials', function() {
});
});
it('adds a new credential',function(done) {
credentials.init();
credentials.init({
log: log,
settings: encryptionDisabledSettings
});
credentials.load({"a":{"b":1,"c":2}}).then(function() {
credentials.dirty().should.be.false;
credentials.dirty().should.be.false();
should.not.exist(credentials.get("b"));
credentials.add("b",{"foo":"bar"}).then(function() {
credentials.get("b").should.have.property("foo","bar");
credentials.dirty().should.be.true;
credentials.dirty().should.be.true();
done();
});
});
});
it('deletes an existing credential',function(done) {
credentials.init();
credentials.init({
log: log,
settings: encryptionDisabledSettings
});
credentials.load({"a":{"b":1,"c":2}}).then(function() {
credentials.dirty().should.be.false;
credentials.dirty().should.be.false();
credentials.delete("a");
should.not.exist(credentials.get("a"));
credentials.dirty().should.be.true;
credentials.dirty().should.be.true();
done();
});
});
it('exports the credentials, clearing dirty flag', function(done) {
credentials.init();
credentials.init({
log: log,
settings: encryptionDisabledSettings
});
var creds = {"a":{"b":1,"c":2}};
credentials.load(creds).then(function() {
credentials.add("b",{"foo":"bar"}).then(function() {
credentials.dirty().should.be.true;
credentials.dirty().should.be.true();
credentials.export().then(function(exported) {
exported.should.eql(creds);
credentials.dirty().should.be.false;
credentials.dirty().should.be.false();
done();
})
});
@@ -81,14 +99,17 @@ describe('red/runtime/nodes/credentials', function() {
describe("#clean",function() {
it("removes credentials of unknown nodes",function(done) {
credentials.init();
credentials.init({
log: log,
settings: encryptionDisabledSettings
});
var creds = {"a":{"b":1,"c":2},"b":{"d":3}};
credentials.load(creds).then(function() {
credentials.dirty().should.be.false;
credentials.dirty().should.be.false();
should.exist(credentials.get("a"));
should.exist(credentials.get("b"));
credentials.clean([{id:"b"}]).then(function() {
credentials.dirty().should.be.true;
credentials.dirty().should.be.true();
should.not.exist(credentials.get("a"));
should.exist(credentials.get("b"));
done();
@@ -96,14 +117,17 @@ describe('red/runtime/nodes/credentials', function() {
});
});
it("extracts credentials of known nodes",function(done) {
credentials.init();
credentials.init({
log: log,
settings: encryptionDisabledSettings
});
credentials.register("testNode",{"b":"text","c":"password"})
var creds = {"a":{"b":1,"c":2}};
var newConfig = [{id:"a",type:"testNode",credentials:{"b":"newBValue","c":"newCValue"}}];
credentials.load(creds).then(function() {
credentials.dirty().should.be.false;
credentials.dirty().should.be.false();
credentials.clean(newConfig).then(function() {
credentials.dirty().should.be.true;
credentials.dirty().should.be.true();
credentials.get("a").should.have.property('b',"newBValue");
credentials.get("a").should.have.property('c',"newCValue");
should.not.exist(newConfig[0].credentials);
@@ -116,7 +140,10 @@ describe('red/runtime/nodes/credentials', function() {
});
it('warns if a node has no credential definition', function(done) {
credentials.init();
credentials.init({
log: log,
settings: encryptionDisabledSettings
});
credentials.load({}).then(function() {
var node = {id:"node",type:"test",credentials:{
user1:"newUser",
@@ -124,7 +151,7 @@ describe('red/runtime/nodes/credentials', function() {
}};
sinon.spy(log,"warn");
credentials.extract(node);
log.warn.called.should.be.true;
log.warn.called.should.be.true();
should.not.exist(node.credentials);
log.warn.restore();
done();
@@ -132,7 +159,10 @@ describe('red/runtime/nodes/credentials', function() {
})
it('extract credential updates in the provided node', function(done) {
credentials.init();
credentials.init({
log: log,
settings: encryptionDisabledSettings
});
var defintion = {
user1:{type:"text"},
password1:{type:"password"},
@@ -155,12 +185,12 @@ describe('red/runtime/nodes/credentials', function() {
user3:"newUser",
password3:"newPassword"
}};
credentials.dirty().should.be.false;
credentials.dirty().should.be.false();
credentials.extract(node);
node.should.not.have.a.property("credentials");
credentials.dirty().should.be.true;
credentials.dirty().should.be.true();
var newCreds = credentials.get("node");
newCreds.should.have.a.property("user1","abc");
newCreds.should.have.a.property("password1","123");
@@ -173,14 +203,269 @@ describe('red/runtime/nodes/credentials', function() {
});
});
it('extract ignores node without credentials', function(done) {
credentials.init();
credentials.init({
log: log,
settings: encryptionDisabledSettings
});
credentials.load({"node":{user1:"abc",password1:"123"}}).then(function() {
var node = {id:"node",type:"test"};
credentials.dirty().should.be.false;
credentials.dirty().should.be.false();
credentials.extract(node);
credentials.dirty().should.be.false;
credentials.dirty().should.be.false();
done();
});
});
describe("encryption",function() {
var settings = {};
var runtime = {
log: log,
settings: {
get: function(key) {
return settings[key];
},
set: function(key,value) {
settings[key] = value;
return when.resolve();
},
delete: function(key) {
delete settings[key];
return when.resolve();
}
}
}
it('migrates to encrypted and generates default key', function(done) {
settings = {};
credentials.init(runtime);
credentials.load({"node":{user1:"abc",password1:"123"}}).then(function() {
settings.should.have.a.property("_credentialSecret");
settings._credentialSecret.should.have.a.length(64);
credentials.dirty().should.be.true();
credentials.export().then(function(result) {
result.should.have.a.property("$");
// reset everything - but with _credentialSecret still set
credentials.init(runtime);
// load the freshly encrypted version
credentials.load(result).then(function() {
should.exist(credentials.get("node"));
done();
})
});
});
});
it('uses default key', function(done) {
settings = {
_credentialSecret: "e3a36f47f005bf2aaa51ce3fc6fcaafd79da8d03f2b1a9281f8fb0a285e6255a"
};
// {"node":{user1:"abc",password1:"123"}}
var cryptedFlows = {"$":"5b89d8209b5158a3c313675561b1a5b5phN1gDBe81Zv98KqS/hVDmc9EKvaKqRIvcyXYvBlFNzzzJtvN7qfw06i"};
credentials.init(runtime);
credentials.load(cryptedFlows).then(function() {
should.exist(credentials.get("node"));
credentials.dirty().should.be.false();
credentials.add("node",{user1:"def",password1:"456"});
credentials.export().then(function(result) {
result.should.have.a.property("$");
// reset everything - but with _credentialSecret still set
credentials.init(runtime);
// load the freshly encrypted version
credentials.load(result).then(function() {
should.exist(credentials.get("node"));
credentials.get("node").should.have.a.property("user1","def");
credentials.get("node").should.have.a.property("password1","456");
done();
})
});
});
});
it('uses user key', function(done) {
settings = {
credentialSecret: "e3a36f47f005bf2aaa51ce3fc6fcaafd79da8d03f2b1a9281f8fb0a285e6255a"
};
// {"node":{user1:"abc",password1:"123"}}
var cryptedFlows = {"$":"5b89d8209b5158a3c313675561b1a5b5phN1gDBe81Zv98KqS/hVDmc9EKvaKqRIvcyXYvBlFNzzzJtvN7qfw06i"};
credentials.init(runtime);
credentials.load(cryptedFlows).then(function() {
credentials.dirty().should.be.false();
should.exist(credentials.get("node"));
credentials.add("node",{user1:"def",password1:"456"});
credentials.export().then(function(result) {
result.should.have.a.property("$");
// reset everything - but with _credentialSecret still set
credentials.init(runtime);
// load the freshly encrypted version
credentials.load(result).then(function() {
should.exist(credentials.get("node"));
credentials.get("node").should.have.a.property("user1","def");
credentials.get("node").should.have.a.property("password1","456");
done();
})
});
});
});
it('uses user key - when settings are otherwise unavailable', function(done) {
var runtime = {
log: log,
settings: {
get: function(key) {
if (key === 'credentialSecret') {
return "e3a36f47f005bf2aaa51ce3fc6fcaafd79da8d03f2b1a9281f8fb0a285e6255a";
}
throw new Error();
},
set: function(key,value) {
throw new Error();
}
}
}
// {"node":{user1:"abc",password1:"123"}}
var cryptedFlows = {"$":"5b89d8209b5158a3c313675561b1a5b5phN1gDBe81Zv98KqS/hVDmc9EKvaKqRIvcyXYvBlFNzzzJtvN7qfw06i"};
credentials.init(runtime);
credentials.load(cryptedFlows).then(function() {
should.exist(credentials.get("node"));
credentials.add("node",{user1:"def",password1:"456"});
credentials.export().then(function(result) {
result.should.have.a.property("$");
// reset everything - but with _credentialSecret still set
credentials.init(runtime);
// load the freshly encrypted version
credentials.load(result).then(function() {
should.exist(credentials.get("node"));
credentials.get("node").should.have.a.property("user1","def");
credentials.get("node").should.have.a.property("password1","456");
done();
})
});
});
});
it('migrates from default key to user key', function(done) {
settings = {
_credentialSecret: "e3a36f47f005bf2aaa51ce3fc6fcaafd79da8d03f2b1a9281f8fb0a285e6255a",
credentialSecret: "aaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbcccccccccccccddddddddddddeeeee"
};
// {"node":{user1:"abc",password1:"123"}}
var cryptedFlows = {"$":"5b89d8209b5158a3c313675561b1a5b5phN1gDBe81Zv98KqS/hVDmc9EKvaKqRIvcyXYvBlFNzzzJtvN7qfw06i"};
credentials.init(runtime);
credentials.load(cryptedFlows).then(function() {
credentials.dirty().should.be.true();
should.exist(credentials.get("node"));
credentials.export().then(function(result) {
result.should.have.a.property("$");
settings.should.not.have.a.property("_credentialSecret");
// reset everything - but with _credentialSecret still set
credentials.init(runtime);
// load the freshly encrypted version
credentials.load(result).then(function() {
should.exist(credentials.get("node"));
credentials.get("node").should.have.a.property("user1","abc");
credentials.get("node").should.have.a.property("password1","123");
done();
})
});
});
});
it('migrates from default key to user key - unencrypted original', function(done) {
settings = {
_credentialSecret: "e3a36f47f005bf2aaa51ce3fc6fcaafd79da8d03f2b1a9281f8fb0a285e6255a",
credentialSecret: "aaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbcccccccccccccddddddddddddeeeee"
};
// {"node":{user1:"abc",password1:"123"}}
var unencryptedFlows = {"node":{user1:"abc",password1:"123"}};
credentials.init(runtime);
credentials.load(unencryptedFlows).then(function() {
credentials.dirty().should.be.true();
should.exist(credentials.get("node"));
credentials.export().then(function(result) {
result.should.have.a.property("$");
settings.should.not.have.a.property("_credentialSecret");
// reset everything - but with _credentialSecret still set
credentials.init(runtime);
// load the freshly encrypted version
credentials.load(result).then(function() {
should.exist(credentials.get("node"));
credentials.get("node").should.have.a.property("user1","abc");
credentials.get("node").should.have.a.property("password1","123");
done();
})
});
});
});
it('migrates from default key to unencrypted', function(done) {
settings = {
_credentialSecret: "e3a36f47f005bf2aaa51ce3fc6fcaafd79da8d03f2b1a9281f8fb0a285e6255a",
credentialSecret: false
};
// {"node":{user1:"abc",password1:"123"}}
var cryptedFlows = {"$":"5b89d8209b5158a3c313675561b1a5b5phN1gDBe81Zv98KqS/hVDmc9EKvaKqRIvcyXYvBlFNzzzJtvN7qfw06i"};
credentials.init(runtime);
credentials.load(cryptedFlows).then(function() {
credentials.dirty().should.be.true();
should.exist(credentials.get("node"));
credentials.export().then(function(result) {
result.should.not.have.a.property("$");
settings.should.not.have.a.property("_credentialSecret");
result.should.eql({"node":{user1:"abc",password1:"123"}});
done();
});
});
});
it('handles bad default key - resets credentials', function(done) {
settings = {
_credentialSecret: "badbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadb"
};
// {"node":{user1:"abc",password1:"123"}}
var cryptedFlows = {"$":"5b89d8209b5158a3c313675561b1a5b5phN1gDBe81Zv98KqS/hVDmc9EKvaKqRIvcyXYvBlFNzzzJtvN7qfw06i"};
credentials.init(runtime);
credentials.load(cryptedFlows).then(function() {
credentials.dirty().should.be.true();
should.not.exist(credentials.get("node"));
done();
});
});
it('handles bad user key - resets credentials', function(done) {
settings = {
credentialSecret: "badbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadb"
};
// {"node":{user1:"abc",password1:"123"}}
var cryptedFlows = {"$":"5b89d8209b5158a3c313675561b1a5b5phN1gDBe81Zv98KqS/hVDmc9EKvaKqRIvcyXYvBlFNzzzJtvN7qfw06i"};
credentials.init(runtime);
credentials.load(cryptedFlows).then(function() {
credentials.dirty().should.be.true();
should.not.exist(credentials.get("node"));
done();
});
});
it('handles unavailable settings - leaves creds unencrypted', function(done) {
var runtime = {
log: log,
settings: {
get: function(key) {
throw new Error();
},
set: function(key,value) {
throw new Error();
}
}
}
// {"node":{user1:"abc",password1:"123"}}
credentials.init(runtime);
credentials.load({"node":{user1:"abc",password1:"123"}}).then(function() {
credentials.dirty().should.be.false();
should.exist(credentials.get("node"));
credentials.export().then(function(result) {
result.should.not.have.a.property("$");
result.should.have.a.property("node");
done();
});
});
});
})
})

View File

@@ -50,12 +50,14 @@ describe("red/nodes/index", function() {
};
var settings = {
available: function() { return false }
available: function() { return false },
get: function() { return false }
};
var runtime = {
settings: settings,
storage: storage
storage: storage,
log: {debug:function(){},warn:function(){}}
};
function TestNode(n) {