Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ JSL_FILES_NODE = $(JS_FILES)
JSSTYLE_FILES = $(JS_FILES)
JSSTYLE_FLAGS = -o indent=4,doxygen,unparenthesized-return=0

#
# Tools
#
NPM_EXEC := npm
TAPE := ./node_modules/.bin/tape

include ./tools/mk/Makefile.defs

Expand All @@ -19,7 +24,11 @@ include ./tools/mk/Makefile.defs
#
.PHONY: all
all:
npm install
$(NPM_EXEC) install

.PHONY: test
test: all
$(TAPE) test/*.test.js

include ./tools/mk/Makefile.deps
include ./tools/mk/Makefile.targ
69 changes: 57 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ Utility functions to sign http requests to SmartDC services.

var signer = auth.privateKeySigner({
key: fs.readFileSync(process.env.HOME + '/.ssh/id_rsa', 'utf8'),
keyId: process.env.SDC_CLI_KEY_ID,
user: process.env.SDC_CLI_ACCOUNT
});

Expand All @@ -27,26 +26,72 @@ The `keyId` for SmartDC is always `/$your_joyent_login/keys/$ssh_fingerprint`,
and the supported algorithms are: `rsa-sha1`, `rsa-sha256` and `dsa-sha1`. You
then just append the base64 encoded signature.

Please, note that at the moment of writing this document, `dsa-sha1` algorithm
does not work with `sshAgentSigner` yet.

## Authenticating Requests

When creating a smartdc client, you'll need to pass in a callback function for
the `sign` parameter. smartdc-auth ships with three functions that will likely
suit your need: `cliSigner`, `privateKeySigner` and `sshAgentSigner`. All of
these callbacks will automatically do the correct crypto for authenticating
requests, the difference is that `privateKeySigner` expects (non-passphrase
protected) keys to be passed in directly (as a file name), whereas `cliSigner`
and `sshAgentSigner` will load your credentials on each request from the SSH
agent (if available). Both callbacks require you to set the account (login)
and keyId (SSH key fingerprint).
the `sign` parameter. smartdc-auth ships with three constructors that return
such functions, which may suit your need: `cliSigner`, `privateKeySigner` and
`sshAgentSigner`.

### `privateKeySigner(options);`

A basic signer which signs using a given PEM (PKCS#1) format private key only.
Ideal for simple use cases where the key is stored in a file on the filesystem
ready for use.

- `options`: an Object containing properties:
- `key`: a String, PEM-format (PKCS#1) private key, for any supported algorithm
- `user`: a String, SDC login name to be used in the full keyId, above
- `subuser`: an optional String, SDC sub-user login name
- `keyId`: optional String, the fingerprint of the `key` (not the same as the
full keyId given to the server). Ignored unless it does not match
the given `key`, then an Error will be thrown.

### `sshAgentSigner(options);`

Signs requests using a key that is stored in the OpenSSH agent. Opens and manages
a connection to the current session's agent during operation.

- `options`: an Object containing properties:
- `keyId`: a String, fingerprint of the key to retrieve from the agent
- `user`: a String, SDC login name to be used
- `subuser`: an optional String, SDC sub-user login name
- `sshAgentOpts`: an optional Object, any additional options to pass through
to the SSHAgent constructor (eg `timeout`)

### `cliSigner(options);`

Signs requests using a key located either in the OpenSSH agent, or found in
the filesystem under `$HOME/.ssh` (or its equivalent on your platform).

This is generally intended for use with CLI utilities (eg the `sdc-listmachines`
tool and family), hence the name.

- `options`: an Object containing properties:
- `keyId`: a String, fingerprint of the key to retrieve or find
- `user`: a String, SDC login name to be used
- `subuser`: an optional String, SDC sub-user login name
- `algorithm`: an optional String, the signing algorithm to use. If this
does not match up with the algorithm of the key (once it is
located), an Error will be thrown.
- `sshAgentOpts`: an optional Object, any additional options to pass through
to the SSHAgent constructor (eg `timeout`)

The `keyId` fingerprint does not necessarily need to be the exact format
(hex MD5) as sent to the server -- it can be in any fingerprint format supported
by the [`sshpk`](https://github.com/arekinath/node-sshpk) library.

As of version 2.0.0, an invalid fingerprint (one that can never match any key,
because, for example, it contains invalid characters) will produce an exception
immediately rather than returning a `sign` function.

Note that the `cliSigner` and `sshAgentSigner` are not suitable for server
applications, or any other system where the performance degradation necessary
to interact with SSH is not acceptable; put another way, you should only use
it for interactive tooling, such as the CLI that ships with node-smartdc.

### Writing your own signer

Should you wish to write a custom plugin, the expected implementation of the
`sign` callback is a function of the form `function (string, callback)`.
`string` is generated by node-smartdc (typically the value of the Date header),
Expand Down
103 changes: 103 additions & 0 deletions bin/sdc-curl
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
#!/usr/bin/env node
// -*- mode: js -*-
// vim: set filetype=javascript :
// Copyright 2015 Joyent, Inc. All rights reserved.

var dashdash = require('dashdash');
var bunyan = require('bunyan');
var spawn = require('child_process').spawn;
var auth = require('../lib/index');
var sprintf = require('util').format;

var options = [
{
names: ['account'],
type: 'string',
help: 'account name',
env: 'SDC_ACCOUNT'
}, {
names: ['user'],
type: 'string',
help: 'account sub-user login',
env: 'SDC_USER'
}, {
names: ['keyId'],
type: 'string',
help: 'your ssh key fingerprint',
env: 'SDC_KEY_ID'
}, {
names: ['manta'],
type: 'bool',
help: 'use manta-style sub-user format'
}, {
names: ['help', 'h'],
type: 'bool',
help: 'print this help and exit'
}
];

if (require.main === module) {
var parser = dashdash.createParser({
options: options,
interspersed: true,
allowUnknown: true
});

try {
var opts = parser.parse(process.argv);
} catch (e) {
console.error('sdc-curl: error: %s', e.message);
process.exit(1);
}

if (opts.help || opts._args.length < 1) {
var help = parser.help({includeEnv: true}).trimRight();
console.log(
'sdc-curl: performs a signed curl request with the same auth\n' +
' creds as the sdc-* family of tools');
console.log('usage: sdc-curl [OPTIONS]\n' +
'options:\n' + help);
console.log('any options other than these will be passed directly to '
+ 'curl');
process.exit(1);
}

var user = opts.account;
if (opts.user !== undefined) {
user = opts.account + '/user/' + opts.user;
if (opts.manta)
user = opts.account + '/' + opts.user;
}

var sign = auth.cliSigner({
user: user,
keyId: opts.keyId
});

var args = opts._args.slice();

var dateHeader = 'date: ' + new Date().toUTCString();

sign(dateHeader, function (err, obj) {
if (err)
throw (err);

var authz = sprintf(
'Signature keyId="/%s/keys/%s",headers="date",' +
'algorithm="%s",signature="%s"',
obj.user, obj.keyId, obj.algorithm, obj.signature);

args.push('-H');
args.push(dateHeader);
args.push('-H');
args.push('Authorization: ' + authz);

var kid = spawn('curl', args);
kid.stdout.pipe(process.stdout);
kid.stderr.pipe(process.stderr);
process.stdin.pipe(kid.stdin);
kid.on('close', function (rc) {
process.exit(rc);
});
});
}
Loading