Skip to content

Commit

Permalink
Baker vault POC.
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisparnin committed Nov 25, 2018
1 parent 82278ec commit 235f144
Show file tree
Hide file tree
Showing 5 changed files with 325 additions and 1 deletion.
3 changes: 2 additions & 1 deletion global-vars.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ module.exports = {
envIndexPath: path.join(boxes, 'data', 'index.json'),
bakerForMacPath,
bakerSSHConfig,
privateKey
privateKey,
// spinnerDot : 'dots'
version: require('./package.json').version
};
96 changes: 96 additions & 0 deletions lib/bakelets/config/vault.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
const Bakelet = require('../bakelet');
const path = require('path');

const { privateKey } = require('../../../global-vars');
const conf = require('../../modules/configstore')

const Ansible = require('../../modules/configuration/ansible');
const VaultLib = require('../../modules/vault');
const Ssh = require('../../modules/ssh');

class Vault extends Bakelet {

constructor(name,ansibleSSHConfig, version) {
super(ansibleSSHConfig);

this.name = name;
this.version = version;
}


async promptPass()
{
return new Promise(function(resolve,reject)
{
var properties = [
{
name: 'password',
hidden: true
}
];

prompt.start();

prompt.get(properties, function (err, result) {
if (err) { reject(err); }
else
{
resolve(result.password)
}
});
});
}


async load(obj, variables)
{

let passphraseKey = `vault:${process.cwd()}`;
let passphrase = '';
if (conf.has(passphraseKey))
{
passphrase = conf.get(passphraseKey);
}
else
{
passphrase = promptPass();
}
let vault = new VaultLib();

if( Array.isArray(obj.vault) )
{
this.vault = obj.vault;
this.variables = variables || {};

for (let entry of obj.vault)
{
let file = path.join(this.bakePath, entry.file);
let content = vault.retrieve(file, passphrase);

await Ssh.writeContentToDest(content,
`/home/vagrant/baker/${this.name}/templates/${entry.file}`,
this.ansibleSSHConfig,
false
);
}
}
}

async install()
{
if( this.vault )
{
for (let entry of this.vault)
{
await Ansible.runAnsibleTemplateCmd(
{name: this.name}, `/home/vagrant/baker/${this.name}/templates/${entry.file}`,
entry.dest, this.variables, this.ansibleSSHConfig, this.verbose);
}
}
}


}

module.exports = Vault;

115 changes: 115 additions & 0 deletions lib/commands/vault.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@

const Baker = require('../modules/baker');
const conf = require('../../lib/modules/configstore')
const Print = require('../modules/print');
const Spinner = require('../modules/spinner');

const fs = require('fs');
const prompt = require('prompt');

const VaultLib = require('../modules/vault');

const spinnerDot = conf.get('spinnerDot');

exports.command = 'vault <file>';
exports.desc = `encrypt a file`;

exports.builder = (yargs) => {
yargs
.example(`$0 vault secret.json`, `Encrypt the secret.json file with a passphrase`)
.example(`$0 vault -v secret.json`, `View unencrypted content with passphrase`)
.example(`$0 vault -u secret.json`, `Unencrypt content with passphrase`)

yargs
.positional('file', {
describe: 'file to encrypt',
type: 'string'
});

yargs.options({
view: {
alias: 'v',
describe: `view unencrypted content with passphrase`,
demand: false,
type: 'boolean'
},
decrypt: {
alias: 'u',
describe: `Unencrypt content with passphrase`,
demand: false,
type: 'boolean'
},
});


}


async function promptPass()
{
return new Promise(function(resolve,reject)
{
var properties = [
{
name: 'password',
hidden: true
}
];

prompt.start();

prompt.get(properties, function (err, result) {
if (err) { reject(err); }
else
{
resolve(result.password)
}
});
});
}


exports.handler = async function(argv) {
let { envName, verbose } = argv;

try{
// await Spinner.spinPromise(BakerObj.start(envName, verbose), `Starting VM: ${envName}`, spinnerDot);
if( !fs.existsSync(argv.file) )
{
throw new Error(`The provide file does not exist: ${argv.file}`)
}

let passphraseKey = `vault:${process.cwd()}`;
if (!conf.has(passphraseKey))
{
let typedPassphrase = await promptPass();
conf.set(passphraseKey, typedPassphrase);
}

let passphrase = conf.get(passphraseKey);
let vault = new VaultLib();

if( argv.decrypt)
{
let content = vault.retrieve(argv.file, passphrase);
console.log("Decrypting contents and writing to file.")
fs.writeFileSync(argv.file, content);
}
else
{
if ( argv.view )
{
let content = vault.retrieve(argv.file, passphrase);
console.log("Viewing decrypted contents:")
console.log(content);
}
else
{
vault.vault(argv.file, passphrase);
}
}

} catch (err){
Print.error(err);
}
}
32 changes: 32 additions & 0 deletions lib/modules/ssh.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const path = require('path');
const print = require('./print')
const scp2 = require('scp2');
const util = require('./utils/utils');
const SCPClient = scp2.Client;

const shell = require('node-powershell');

Expand Down Expand Up @@ -437,6 +438,37 @@ class Ssh {
});
};


static async writeContentToDest(content, dest, destSSHConfig) {
let Ssh = this;

return new Promise((resolve, reject) => {

var client = new SCPClient({
host: '127.0.0.1',
port: destSSHConfig.port,
username: destSSHConfig.user,
privateKey: fs.readFileSync(destSSHConfig.private_key, 'utf8'),
});

client.write({
destination: dest,
content: Buffer.from(content)
}, function(err)
{
client.close();

if( err ) {
reject();
} else {
resolve();
}
});

});
}


static async copyFromHostToVM(src, dest, destSSHConfig, chmod_ = true) {
let Ssh = this;

Expand Down
80 changes: 80 additions & 0 deletions lib/modules/vault.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
const crypto = require('crypto');
const fs = require('fs');
const path = require('path');

const {version} = require('../../global-vars')

class VaultLib {
constructor()
{
this.iv = crypto.randomBytes(16);
this.algorithm = 'aes-256-ctr';
this.salt = Buffer.from('5ebe2294ecd0e0f', 'hex');
this.headerRegex = /baker-vault:\d+[.]\d+[.]\d+:[0-9A-Fa-f]{15}/g;
}

isEncrypted(filePath)
{
let lines = fs.readFileSync(filePath).toString().split(/\r?\n/);
if( lines.length > 0 )
{
let matches = lines[0].match( this.headerRegex );
return matches != null;
}
return false;
}

vault(filePath, passphrase)
{
if( this.isEncrypted(filePath) )
{
throw new Error(`File is already encrypted: ${filePath}`);
}

let key = this.generateKeyFromPhrase(passphrase);
let content = fs.readFileSync(filePath);

let buffer = `baker-vault:${version}:${this.iv.toString('hex')}\n`;
buffer += this.encryptWithKey(content, key, this.iv);

fs.writeFileSync(filePath, buffer);

return key.toString('hex');
}

retrieve(filePath, passphrase)
{
if( this.isEncrypted(filePath) )
{
let lines = fs.readFileSync(filePath).toString().split(/\r?\n/);
let key = this.generateKeyFromPhrase(passphrase);
let iv = Buffer.from(lines[0].split(':')[2],'hex');

return this.decryptWithKey(lines[1],key,iv);
}
throw new Error(`File is not encrypted: ${filePath}`);
}

encryptWithKey(text, key, iv) {
let cipher = crypto.createCipheriv(this.algorithm, key, iv )
let crypted = cipher.update(text,'utf8','hex')
crypted += cipher.final('hex');
return crypted;
}

generateKeyFromPhrase(passphrase)
{
return crypto.pbkdf2Sync(passphrase, this.salt, 100000, 32, 'sha512');
}

decryptWithKey(text, key, iv)
{
let decipher = crypto.createDecipheriv(this.algorithm, key, iv)
let dec = decipher.update(text,'hex','utf8')
dec += decipher.final('utf8');
return dec;
}
}

module.exports = VaultLib;

0 comments on commit 235f144

Please sign in to comment.