joyent/node-triton#150 fallback to ~/.ssh during docker profile creation

Reviewed by: Todd Whiteman <todd.whiteman@joyent.com>
Approved by: Todd Whiteman <todd.whiteman@joyent.com>
This commit is contained in:
Trent Mick 2017-02-20 19:39:16 -08:00
parent 86c689e809
commit dba8915d4f
2 changed files with 70 additions and 21 deletions

View File

@ -359,7 +359,7 @@ function _createProfile(opts, cb) {
console.log(wrap80('This section will setup authentication to ' + console.log(wrap80('This section will setup authentication to ' +
'Triton DataCenter\'s Docker endpoint using your account ' + 'Triton DataCenter\'s Docker endpoint using your account ' +
'and key information specified above. This is only required ' + 'and key information specified above. This is only required ' +
'if you intend to use `docker` with this profile.')); 'if you intend to use `docker` with this profile.\n'));
profilecommon.profileDockerSetup({ profilecommon.profileDockerSetup({
cli: cli, cli: cli,

View File

@ -141,15 +141,11 @@ function setCurrentProfile(opts, cb) {
* - {Boolean} implicit: Optional. Boolean indicating if the Docker setup * - {Boolean} implicit: Optional. Boolean indicating if the Docker setup
* is implicit (e.g. as a default part of `triton profile create`). If * is implicit (e.g. as a default part of `triton profile create`). If
* implicit, we silently skip if ListServices shows no Docker service. * implicit, we silently skip if ListServices shows no Docker service.
* - {Object} keyPaths: Optional. An object with `private` and/or `public`
* properties pointing to a full path to an SSH private and/or public
* key to use for cert signing.
*/ */
function profileDockerSetup(opts, cb) { function profileDockerSetup(opts, cb) {
assert.object(opts.cli, 'opts.cli'); assert.object(opts.cli, 'opts.cli');
assert.string(opts.name, 'opts.name'); assert.string(opts.name, 'opts.name');
assert.optionalBool(opts.implicit, 'opts.implicit'); assert.optionalBool(opts.implicit, 'opts.implicit');
assert.optionalObject(opts.keyPaths, 'opts.keyPaths');
assert.func(cb, 'cb'); assert.func(cb, 'cb');
var cli = opts.cli; var cli = opts.cli;
@ -163,7 +159,7 @@ function profileDockerSetup(opts, cb) {
vasync.pipeline({arg: {tritonapi: tritonapi}, funcs: [ vasync.pipeline({arg: {tritonapi: tritonapi}, funcs: [
function dockerKeyWarning(arg, next) { function dockerKeyWarning(arg, next) {
console.log(wordwrap('\nWARNING: Docker uses authentication via ' + console.log(wordwrap('WARNING: Docker uses authentication via ' +
'client TLS certificates that do not support encrypted ' + 'client TLS certificates that do not support encrypted ' +
'(passphrase protected) keys or SSH agents. If you continue,' + '(passphrase protected) keys or SSH agents. If you continue,' +
'this profile setup will attempt to write a copy of your ' + 'this profile setup will attempt to write a copy of your ' +
@ -254,18 +250,6 @@ function profileDockerSetup(opts, cb) {
next(); next();
}, },
function checkSshPrivKey(arg, next) {
try {
tritonapi.keyPair.getPrivateKey();
} catch (e) {
next(new errors.SetupError(format('could not obtain SSH ' +
'private key for keypair with fingerprint "%s" ' +
'to create Docker certificate.', profile.keyId)));
return;
}
next();
},
/* /*
* Find the `docker` version, if we can. This can be used later to * Find the `docker` version, if we can. This can be used later to
* control some envvars that depend on the docker version. * control some envvars that depend on the docker version.
@ -319,6 +303,72 @@ function profileDockerSetup(opts, cb) {
}); });
}, },
/*
* We need the private key to format as a client cert. If this profile's
* key was found in the SSH agent (and by default it prefers to take
* it from there), then we can't use `tritonapi.keyPair`, because
* the SSH agent protocol will not allow us access to the private key
* data (by design).
*
* As a fallback we'll look (via KeyRing) for a local copy of the
* private key to use, and then unlock it if necessary.
*/
function getPrivKey(arg, next) {
// If the key pair already works, then use that...
try {
arg.privKey = tritonapi.keyPair.getPrivateKey();
next();
return;
} catch (_) {
// ... else fall through.
}
var kr = new auth.KeyRing();
kr.list(function (listErr, keyMap) {
if (listErr) {
next(listErr);
return;
}
/*
* If our keyId was found, and with the 'homedir' plugin, then
* we should have access to the private key (modulo unlocking).
*/
var keyPairs = keyMap[tritonapi.profile.keyId] || [];
var homedirKeyPair;
for (var i = 0; i < keyPairs.length; i++) {
if (keyPairs[i].plugin === 'homedir') {
homedirKeyPair = keyPairs[i];
break;
}
}
if (homedirKeyPair) {
common.promptPassphraseUnlockKey({
// Fake the `tritonapi` object, only `.keyPair` is used.
tritonapi: {keyPair: homedirKeyPair}
}, function (unlockErr) {
if (unlockErr) {
next(unlockErr);
return;
}
try {
arg.privKey = homedirKeyPair.getPrivateKey();
} catch (homedirErr) {
next(new errors.SetupError(homedirErr, format(
'could not obtain SSH private key for keyId ' +
'"%s" to create Docker certificate',
profile.keyId)));
return;
}
next();
});
} else {
next(new errors.SetupError(format('could not obtain SSH ' +
'private key for keyId "%s" to create Docker ' +
'certificate', profile.keyId)));
}
});
},
function genClientCert_dir(arg, next) { function genClientCert_dir(arg, next) {
arg.dockerCertPath = path.resolve(cli.configDir, arg.dockerCertPath = path.resolve(cli.configDir,
@ -327,7 +377,7 @@ function profileDockerSetup(opts, cb) {
}, },
function genClientCert_key(arg, next) { function genClientCert_key(arg, next) {
arg.keyPath = path.resolve(arg.dockerCertPath, 'key.pem'); arg.keyPath = path.resolve(arg.dockerCertPath, 'key.pem');
var data = tritonapi.keyPair.getPrivateKey().toBuffer('pkcs1'); var data = arg.privKey.toBuffer('pkcs1');
fs.writeFile(arg.keyPath, data, function (err) { fs.writeFile(arg.keyPath, data, function (err) {
if (err) { if (err) {
next(new errors.SetupError(err, format( next(new errors.SetupError(err, format(
@ -340,9 +390,8 @@ function profileDockerSetup(opts, cb) {
function genClientCert_cert(arg, next) { function genClientCert_cert(arg, next) {
arg.certPath = path.resolve(arg.dockerCertPath, 'cert.pem'); arg.certPath = path.resolve(arg.dockerCertPath, 'cert.pem');
var privKey = tritonapi.keyPair.getPrivateKey();
var id = sshpk.identityFromDN('CN=' + profile.account); var id = sshpk.identityFromDN('CN=' + profile.account);
var cert = sshpk.createSelfSignedCertificate(id, privKey); var cert = sshpk.createSelfSignedCertificate(id, arg.privKey);
var data = cert.toBuffer('pem'); var data = cert.toBuffer('pem');
fs.writeFile(arg.certPath, data, function (err) { fs.writeFile(arg.certPath, data, function (err) {