Skip to content
Open
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
7 changes: 6 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,9 @@
* Copyright (c) 2014, Joyent, Inc.
*/

module.exports = require('./lib/client.js');
var client = require('./lib/client.js');
var scopeSchema = require('./lib/scope-schema.js');
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the coordination problem due to the "microservices" nature of current Manta & Triton?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need the scope-schema to be in sync between consumers, so we share this here.


client.scopeSchema = scopeSchema;

module.exports = client;
136 changes: 124 additions & 12 deletions lib/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ var LRU = require('lru-cache');
var httpSignature = require('http-signature');
var qs = require('querystring');
var restify = require('restify');
var scopeSchema = require('./scope-schema.js');
var sprintf = require('util').format;


Expand Down Expand Up @@ -797,7 +798,7 @@ MahiClient.prototype.getLookup = function getLookup(opts, cb) {
*
* accessKeyId: AWS access key ID (e.g., "AKIA123456789EXAMPLE")
* cb: callback in the form fn(err, userInfo)
*
*
* Returns user object without access key secrets:
* {
* type: "account",
Expand All @@ -811,14 +812,14 @@ MahiClient.prototype.getLookup = function getLookup(opts, cb) {
* AccessKeyNotFoundError
* RedisError
*/
MahiClient.prototype.getUserByAccessKey = function
MahiClient.prototype.getUserByAccessKey = function
getUserByAccessKey(accessKeyId, cb) {
assert.string(accessKeyId, 'accessKeyId');
assert.func(cb, 'callback');

var self = this;
var path = sprintf('/aws-auth/%s', accessKeyId);

self.http.get(path, function (err, req, res, obj) {
if (err) {
cb(err);
Expand All @@ -831,42 +832,41 @@ MahiClient.prototype.getUserByAccessKey = function

/**
* Verify AWS Signature Version 4 authentication
*
*
* request: HTTP request object with AWS4-HMAC-SHA256 authorization header
* cb: callback in the form fn(err, result)
*
* Returns:
* {
* valid: true,
* accessKeyId: "AKIA123456789EXAMPLE",
* accessKeyId: "AKIA123456789EXAMPLE",
* userUuid: "user-uuid"
* }
*
* errors:
* InvalidSignatureError
* AccessKeyNotFoundError
* AccessKeyNotFoundError
* RequestTimeTooSkewedError
*/
MahiClient.prototype.verifySigV4 = function verifySigV4(request, cb) {
assert.object(request, 'request');
assert.func(cb, 'callback');

var self = this;

// Forward the original headers to mahi for SigV4 verification
var requestOptions = {
path: '/aws-verify',
headers: request.headers
};

// Add request method and URL as query parameters since mahi needs them for verification
var qs = require('querystring');

/* Add method and URL as query parameters for mahi verification */
var queryParams = {
method: request.method,
url: request.url
};
requestOptions.path += '?' + qs.stringify(queryParams);

self.http.post(requestOptions, {}, function (err, req, res, obj) {
if (err) {
cb(err);
Expand All @@ -877,6 +877,118 @@ MahiClient.prototype.verifySigV4 = function verifySigV4(request, cb) {
};


/**
* @brief Push an access key to mahi's Redis cache
*
* Writes the key directly to Redis, bypassing the
* UFDS replication delay. Best-effort: errors are
* logged but not propagated to the caller.
*
* @param {Object} opts
* opts.accesskeyid: {string} Key ID (required)
* opts.accesskeysecret: {string} Secret (required)
* opts.ownerUuid: {string} Owner UUID (required)
* opts.status: {string} 'Active' or 'Inactive'
* opts.scope: {string|null} Scope JSON
* @param {Function} cb - callback(err)
*/
MahiClient.prototype.cachePush =
Comment thread
danmcd marked this conversation as resolved.
function cachePush(opts, cb) {
assert.object(opts, 'opts');
assert.string(opts.accesskeyid, 'opts.accesskeyid');
assert.string(opts.accesskeysecret,
'opts.accesskeysecret');
assert.string(opts.ownerUuid, 'opts.ownerUuid');
assert.optionalString(opts.status, 'opts.status');
assert.optionalString(opts.scope, 'opts.scope');
assert.func(cb, 'callback');

/*
* Validate scope at the client boundary to prevent
* malformed scope strings from poisoning the Redis
* cache. Null/undefined scope is valid (unrestricted
* key).
*/
if (opts.scope) {
var parsed = scopeSchema.parseScope(opts.scope);
if (parsed === null) {
cb(new Error('cachePush: opts.scope is not' +
' valid scope JSON'));
return;
}
var result = scopeSchema.validateScope(parsed);
if (!result.valid) {
cb(new Error('cachePush: invalid scope: ' +
result.error));
return;
}
}

var self = this;
var path = sprintf('/cache-push/%s',
opts.accesskeyid);
var body = {
accesskeysecret: opts.accesskeysecret,
ownerUuid: opts.ownerUuid,
status: opts.status || 'Active',
scope: opts.scope || null
};

self.http.post(path, body,
function (err, req, res, obj) {
if (err) {
if (self.http.log) {
self.http.log.warn({
err: err,
accesskeyid: opts.accesskeyid
}, 'cachePush: mahi call failed' +
' (non-fatal)');
}
cb(err);
return;
}
cb(null, obj);
});
};


/**
* @brief Revoke an access key from mahi's Redis cache
*
* Deletes the key immediately from Redis, bypassing
* the UFDS replication delay. Best-effort: errors
* are logged but not propagated.
*
* @param {string} accesskeyid - Key ID to revoke
* @param {Function} cb - callback(err)
*/
MahiClient.prototype.scopeRevoke =
function scopeRevoke(accesskeyid, cb) {
assert.string(accesskeyid, 'accesskeyid');
assert.func(cb, 'callback');

var self = this;
var path = sprintf('/key-revoke/%s',
accesskeyid);

self.http.post(path, {},
function (err, req, res, obj) {
if (err) {
if (self.http.log) {
self.http.log.warn({
err: err,
accesskeyid: accesskeyid
}, 'scopeRevoke: mahi call failed' +
' (non-fatal)');
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For example, wouldn't this line and the prior on join together so it looks like:

        if (err) {                  
            if (self.http.log) {
                self.http.log.warn({
                    err: err,
                    accesskeyid: accesskeyid
                }, 'scopeRevoke: mahi call failed (non-fatal)');
            }

}
cb(err);
return;
}
cb(null, obj);
});
};


module.exports = {
MahiClient: MahiClient,
createClient: function (opts) {
Expand Down
Loading