GithubHelp home page GithubHelp logo

tritondatacenter / node-smartdc-auth Goto Github PK

View Code? Open in Web Editor NEW
3.0 37.0 9.0 155 KB

Utility library for authenticating to Triton and Manta services

License: MIT License

Makefile 27.42% JavaScript 72.58%

node-smartdc-auth's Introduction

Triton Authentication Library

Utility functions to sign http requests to Triton and Manta services. This library is meant to be used internally by other libraries and tools as in the triton and Manta repositories.

If you only want to use one of these libraries to make requests to a Triton service, you should not need to use this library directly at all.

Its API can be used independently, though, to search for and list the available SSH keys on the system (used by triton profile create, for example):

var mod_sdcauth = require('smartdc-auth');

var keyRing = new mod_sdcauth.KeyRing();
keyRing.list(function (err, keyMap) {
    if (err) {
        /* ... handle err ... */
        return;
    }
    /* The keyMap is an object that maps keyId => [keyPair] */
    var keyIds = Object.keys(keyMap);
    keyIds.forEach(function (keyId) {
        var keys = keyMap[keyId];
        console.log('%s:', keyId);
        keys.forEach(function (keyPair) {
            var key = keyPair.getPublicKey();
            console.log('  %s (%d bit): %s',
              key.type, key.size, key.comment);
            if (keyPair.isLocked())
                console.log('    !! password protected');
        });
    });
});

This might produce the output:

05:6c:c8:0c:83:6c:1e:9a:81:26:fb:52:8e:03:3c:33:
  ecdsa (256 bit): [email protected]
    !! password protected
2c:be:e8:b1:32:02:31:cd:10:89:f9:96:95:db:11:0c:
  rsa (2048 bit): [email protected]
81:ad:d5:57:e5:6f:7d:a2:93:79:56:af:d7:c0:38:51:
  ecdsa (256 bit): [email protected]

It can also be used to implement your own http-signature HTTPS client that uses the same logic that the triton and manta tools do to locate SSH keys:

var mod_sdcauth = require('smartdc-auth');
var mod_sshpk = require('sshpk');
var mod_https = require('https');

var fp = mod_sshpk.parseFingerprint(process.env.TRITON_KEY_ID);

var keyRing = new mod_sdcauth.KeyRing();
keyRing.findSigningKeyPair(fp, function (err, keyPair) {
    var signer = keyPair.createRequestSigner({
        user: process.env.TRITON_ACCOUNT
    });
    var opts = {
        host: 'localhost',
        port: 8443, path: '/', method: 'GET',
        headers: {}
    };
    signer.writeTarget(opts.method, opts.path);
    opts.headers.date = signer.writeDateHeader();
    signer.sign(function (err, authz) {
        opts.headers.authorization = authz;
        var req = https.request(opts);
        /* ... */
        req.end();
    });
});

Overview

Authentication to Triton CloudAPI and Manta is built on top of the http-signature specification. All requests to the APIs require an HTTP Authorization header where the scheme is Signature. Full details are available in the http-signature specification, but a simple form is:

Authorization: Signature keyId="/:login/keys/:md5_fingerprint",algorithm="rsa-sha256" $base64_signature

The keyId field varies in structure when making requests with RBAC subusers, particularly when doing so in requests made to Manta. In the API reference below, the term keyId generally refers specifically to the MD5 fingerprint of the key in hex format, as used in the field.

Note that this MD5 fingerprint is used only to choose the existing full key on file at the server end out of the ones for the given user and is not used for authentication itself (so the weak hash is not a serious problem).

This library handles the complete process of finding SSH keys based on user preferences or input, all the way to generating the contents of the Authorization header ready for you to use.

The general idea is to create a KeyRing, then search it for the particular key pair you want to use. Then you can call methods on the KeyPair instance like createRequestSigner() to sign an HTTP request. You can also access metadata about the key pair.

API: KeyRing

new mod_sdcauth.KeyRing([options])

Create a new SDC keyring. KeyRing instances use a list of plugins in order to locate keys on the local system - via the filesystem, via the SSH agent, or any other mechanism.

Parameters

  • options: an Object containing properties:
    • plugins: an Array of Strings, names of plugins to enable

Any additional keys set in the options object will be passed through to plugins as options for their processing.

Available plugins:

  • agent: Gets keys from the OpenSSH agent. Options:
    • sshAgentOpts: an Object, options to be passed to mod_sshpk_agent.Client
  • homedir: Gets keys from a directory on the filesystem. Options:
    • keyDir: a String, path to look in for keys, defaults to $HOME/.ssh
  • file: Gets a key from a particular path on disk. Options:
    • keyPath: a String, path to the private key file

KeyRing#addPlugin(pluginName[, options])

Adds a plugin to the KeyRing after construction. This is particularly useful with the file plugin.

Parameters

  • pluginName: a String, name of the plugin to load. One of agent, homedir or file
  • options: an optional Object, options to pass to the plugin. See the documentation above for the class constructor for details.

KeyRing#list(cb)

Lists all available keys in all plugins, organised by their Key ID.

Parameters

  • cb: a Function (err, keyPairs) with parameters:
    • err: an Error or null
    • keyPairs: an Object, keys: String key IDs, values: Array of instances of KeyPair

KeyRing#find(fingerprint, cb)

Searches active plugins for an SSH key matching the given fingerprint. Calls cb with an array of KeyPair instances that match, ordered arbitrarily.

Parameters:

  • fingerprint: an sshpk.Fingerprint
  • cb: a Function (err, keyPairs), with parameters:
    • err: an Error or null
    • keyPairs: an Array of KeyPair instances

KeyRing#findSigningKeyPair(fingerprint, cb)

Searches active plugins for an SSH key matching the given fingerprint. Chooses the best available signing key of those available (preferably unlocked) and calls cb with this single KeyPair instance.

Parameters:

  • fingerprint: an sshpk.Fingerprint
  • cb: a Function (err, keyPair), with parameters:
    • err: an Error or null
    • keyPair: a KeyPair instance

KeyPair

KeyPair.fromPrivateKey(privKey)

Constructs a KeyPair unrelated to any keychain, based directly on a given private key. This is mostly useful for compatibility purposes.

Parameters:

  • privKey: an sshpk.PrivateKey

KeyPair#plugin

String, name of the plugin through which this KeyPair was found.

KeyPair#source

String (may be undefined), human-readable name of the source that the KeyPair came from when discovered (e.g. for a plugin that searches the filesystem, this could be the path to the key file).

KeyPair#comment

String, comment that was stored with the key, if any.

KeyPair#canSign()

Returns Boolean true if this key pair is complete (has a private and public key) and can be used for signing. Note that this returns true for locked keys.

KeyPair#isLocked()

Returns Boolean true if this key pair is locked and may be unlocked using the unlock() method.

KeyPair#unlock(passphrase)

Unlocks an encrypted key pair, allowing it to be used for signing and the getPrivateKey() method to be called.

Parameters:

  • passphrase: a String, passphrase for decryption

KeyPair#getKeyId()

Returns the String key ID for this key pair. This is specifically the key ID as used in HTTP signature auth for SDC and Manta. Currently this is a hex-format MD5 fingerprint of the key, but this may change in future.

KeyPair#getPublicKey()

Returns the sshpk.Key object representing this pair's public key.

KeyPair#getPrivateKey()

Returns the sshpk.PrivateKey object representing this pair's private key. If unavailable, this method will throw an Error.

KeyPair#createRequestSigner(options)

Creates an http-signature RequestSigner object for signing an HTTP request using this key pair's private key.

Parameters:

  • options, an Object with keys:
    • user, a String, the Triton or Manta account to authenticate as. Note that this field is named user even though it normally refers to an account, for historical reasons.
    • subuser, an optional String, subuser of the account to authenticate as
    • mantaSubUser, an optional Boolean, if true use Manta-style subuser syntax

KeyPair#createSign(options)

Creates a sign() function (matching the legacy smartdc-auth API) for signing arbitrary data with this key pair's private key.

Parameters:

  • options, an Object with keys:
    • user, a String, the Triton or Manta account to authenticate as. Note that this field is named user even though it normally refers to an account, for historical reasons.
    • subuser, an optional String, subuser of the account to authenticate as
    • mantaSubUser, an optional Boolean, if true use Manta-style subuser syntax
    • algorithm, an optional String, the signing algorithm to use

Legacy request signers

Older SDC and Manta client libraries expose a bit more of the innards of key location and management, and require direct use of this library.

The legacy signer function API is provided for compatibility with users of these older client libraries. Note that you don't need to use this API for new software that still wants to be able to use an older client library (you can just use the createSign() method on a KeyPair, above).

These functions take options and return a "signer function" which is provided as the sign parameter to other libraries.

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 subuser 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 subuser 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 subuser login name
    • sshAgentOpts: an optional Object, any additional options to pass through to the SSHAgent constructor (eg timeout)
    • algorithm: DEPRECATED, 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.

(The algorithm option is deprecated as its backwards-compatible behaviour is to apply only to keys that were found on disk, not in the SSH agent. If you have a compelling use case for a replacement for this option in future, please open an issue on this repo).

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 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.

License

MIT.

Bugs

See https://github.com/TritonDataCenter/node-smartdc-auth/issues.

node-smartdc-auth's People

Contributors

arekinath avatar bahamat avatar cburroughs avatar kusor avatar melloc avatar travispaul avatar trentm avatar

Stargazers

 avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

node-smartdc-auth's Issues

need to support Buffers as arguments to signer

The signers returned by createSign() on a KeyPair should support Buffers as well as Strings as their arguments, so that they can be used to sign other data (like certificates for DOCKER-1005)

privateKeySigner doesn't allow opts.key to be a buffer

It has:

	if (typeof (options.key) !== 'string' &&
	    !Buffer.isBuffer(options.key)) {
		throw (new Error('options.key (a String or Buffer) is ' +
		    'required'));
	}
	mod_assert.string(options.key, 'options.key');

The first allows a buffer, but then the (I assume accidentally left in) next one requires a string.

requestSigner() regression: duplicated prefixes on keyId

With 2.5.0 and a client that uses the legacy requestSigner() API, you can end up with duplicated prefixes on the keyId due to caching. This is easily observed with manta:

[2016-08-19T03:33:17.053Z] TRACE: mls/MantaClient/7308 on myra.home.cooperi.net (/home/alex/dev/node-manta/node_modules/restify-clients/lib/HttpClient.js:314 in rawRequest): request sent
    GET /arekinath/stor?limit=1024 HTTP/1.1
    Host: us-east.manta.joyent.com
    accept: application/x-json-stream
    x-request-id: f3b44b99-82f8-4096-9e2d-f7e3bad443a0
    date: Fri, 19 Aug 2016 03:33:17 GMT
    authorization: Signature keyId="/arekinath/keys//arekinath/keys/5e:ff:e9:7b:3a:a2:97:b9:67:bb:c0:8b:0b:55:a0:1f",algorithm="ecdsa-sha256",headers="date",signature="MEQCIC/c4ERV2yAUAfnahYu1baMTiw8OxhjO3n1PblJQGQnmAiBTQEvrAgghiftusCaZNmp/y4z1tawprsjzfyd1GJOqOA=="
    user-agent: restify/1.3.0 (x64-linux; v8/3.28.71.19; OpenSSL/1.0.1q) node/0.12.9
    accept-version: ~1.0
[2016-08-19T03:33:17.412Z] TRACE: mls/MantaClient/7308 on myra.home.cooperi.net (/home/alex/dev/node-manta/node_modules/restify-clients/lib/HttpClient.js:210 in onResponse): Response received (client_res={})
    HTTP/1.1 403 Forbidden
    content-type: application/json
    content-length: 75
    content-md5: NAEF3BpETvs9B2BVeDtX8A==
    date: Fri, 19 Aug 2016 03:33:17 GMT
    server: Manta
    x-request-id: f3b44b99-82f8-4096-9e2d-f7e3bad443a0
    x-response-time: 1
    x-server-name: 60771e58-2ad0-4c50-8b23-86b72f9307f8
    connection: keep-alive
    x-request-received: 1471577597051
    x-request-processing-time: 360
[2016-08-19T03:33:17.418Z] DEBUG: mls/MantaClient/7308 on myra.home.cooperi.net (/home/alex/dev/node-manta/lib/client.js:797): get: error (req_id=e71baada-09ca-4f6f-b56f-d47160837b64, path=/arekinath/stor, err.code=InvalidKeyId)
    InvalidKeyIdError: the KeyId token you provided is invalid
        at ClientRequest.onResponse (/home/alex/dev/node-manta/node_modules/restify-clients/lib/HttpClient.js:217:26)
        at ClientRequest.g (events.js:199:16)
        at ClientRequest.emit (events.js:107:17)
        at HTTPParser.parserOnIncomingClient (_http_client.js:426:21)
        at HTTPParser.parserOnHeadersComplete (_http_common.js:111:23)
        at TLSSocket.socketOnData (_http_client.js:317:20)
        at TLSSocket.emit (events.js:107:17)
        at readableAddChunk (_stream_readable.js:163:16)
        at TLSSocket.Readable.push (_stream_readable.js:126:10)
        at TCP.onread (net.js:540:20)

Notice /arekinath/keys//arekinath/keys/5e:ff:e9:7b:3a:a2:97:b9:67:bb:c0:8b:0b:55:a0:1f in the keyId

bump the signaturecache event emitter limit

Occasionally with a mfind ... | mjob create ... using node v4, one gets this warning:

(node) warning: possible EventEmitter memory leak detected. 11 SHA256:j0NhFfZoTyHo8j0hUp4ZdbrjO4Nopdg5z33B3YsHi5k|date: Thu, 03 Nov 2016 17:59:52 GMT listeners added. Use emitter.setMaxListeners() to increase limit.
Trace
   at EventEmitter.addListener (events.js:239:17)
   at EventEmitter.once (events.js:265:8)
   at SignatureCache.get (/Users/trentm/joy/node-manta/node_modules/smartdc-auth/lib/keyring.js:238:16)
   at sign (/Users/trentm/joy/node-manta/node_modules/smartdc-auth/lib/kr-agent.js:132:13)
   at sign (/Users/trentm/joy/node-manta/node_modules/smartdc-auth/lib/index.js:176:3)
   at RequestSigner.rsign [as rs_signFunc] (/Users/trentm/joy/node-manta/node_modules/smartdc-auth/lib/index.js:131:3)
   at RequestSigner.sign (/Users/trentm/joy/node-manta/node_modules/http-signature/lib/signer.js:180:10)
   at MantaClient.signRequest (/Users/trentm/joy/node-manta/lib/client.js:652:8)
   at next (/Users/trentm/joy/node-manta/lib/client.js:1532:14)
   at MantaClient.ls (/Users/trentm/joy/node-manta/lib/client.js:1620:9)

Alex suggests we "bump the signaturecache event emitter limit
because we expect it to be used a lot"

KeyRingHomeDirPlugin.listKeys() invokes callback twice on failure

If listKeys() fails while invoking _iter(), then it will call the callback twice, and blow up later on in a vasync assertion:

assert.js:81
  throw new assert.AssertionError({
  ^
AssertionError: 'fail' == 'pending'
    at /usr/local/lib/node_modules/triton/node_modules/smartdc-auth/node_modules/vasync/lib/vasync.js:62:15
    at /usr/local/lib/node_modules/triton/node_modules/smartdc-auth/lib/keyring.js:120:4
    at /usr/local/lib/node_modules/triton/node_modules/smartdc-auth/lib/kr-homedir.js:82:3
    at /usr/local/lib/node_modules/triton/node_modules/smartdc-auth/lib/kr-homedir.js:119:4
    at FSReqWrap.oncomplete (fs.js:114:15)

It should return after passing the error to the callback.

Agent errors result in useless exception

Currently if the ssh-agent denies an attempt to generate a signature (e.g. because it's a piv-agent and no PIN has been supplied) you get an exception stack like this:

[alex@mildred node-triton]$ ./bin/triton ls

assert.js:86
  throw new assert.AssertionError({
        ^
AssertionError: value
    at SignatureCache.put (/home/alex/dev/node-triton/node_modules/smartdc-auth/lib/keyring.js:255:13)
    at /home/alex/dev/node-triton/node_modules/smartdc-auth/lib/kr-agent.js:138:11
    at Request.r_cb (/home/alex/dev/node-triton/node_modules/sshpk-agent/lib/client.js:139:4)
    at Request.state_done (/home/alex/dev/node-triton/node_modules/sshpk-agent/lib/client-fsm.js:223:8)
    at Request.FSM._gotoState (/home/alex/dev/node-triton/node_modules/sshpk-agent/node_modules/mooremachine/lib/fsm.js:325:4)
    at FSMStateHandle.gotoState (/home/alex/dev/node-triton/node_modules/sshpk-agent/node_modules/mooremachine/lib/fsm.js:76:23)
    at Client.<anonymous> (/home/alex/dev/node-triton/node_modules/sshpk-agent/lib/client-fsm.js:189:5)
    at Client.emit (events.js:107:17)
    at AgentDecodeStream.<anonymous> (/home/alex/dev/node-triton/node_modules/sshpk-agent/lib/client-fsm.js:365:9)
    at AgentDecodeStream.emit (events.js:104:17)

We should stop trying to put errors into the cache in kr-agent.js, so that this comes out as this instead:

[alex@mildred node-triton]$ ./bin/triton ls
triton instance list: error (Signing): error signing request: SSH agent returned failure, no reason given

Which at least tells the user what to look at.

KeyPair isKey assertion should be min-versioned

Currently when there's an sshpk release newer than the sshpk-agent one available and there's a Key format change, you can get errors like this:

mls: AssertionError: options.public must be a sshpk.Key instance
    at AgentKeyPair.SDCKeyPair (/usr/local/lib/node_modules/manta/node_modules/smartdc-auth/lib/keypair.js:50:14)
    at new AgentKeyPair (/usr/local/lib/node_modules/manta/node_modules/smartdc-auth/lib/kr-agent.js:93:10)
    at /usr/local/lib/node_modules/manta/node_modules/smartdc-auth/lib/kr-agent.js:81:14
    at Request.r_cb (/usr/local/lib/node_modules/manta/node_modules/sshpk-agent/lib/client.js:59:3)
    at Request.state_done (/usr/local/lib/node_modules/manta/node_modules/sshpk-agent/lib/client-fsm.js:224:8)
    at Request.FSM._gotoState (/usr/local/lib/node_modules/manta/node_modules/mooremachine/lib/fsm.js:325:4)
    at FSMStateHandle.gotoState (/usr/local/lib/node_modules/manta/node_modules/mooremachine/lib/fsm.js:76:23)
at Client.<anonymous> (/usr/local/lib/node_modules/manta/node_modules/sshpk-agent/lib/client-fsm.js:190:5)
    at emitOne (events.js:96:13)
    at Client.emit (events.js:188:7)

This is because in keypair.js we're using Key.isKey from sshpk in an assertion without giving it a minimum version we require, so it's assuming we need the latest.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.