Skip to content
Open
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;
130 changes: 118 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,112 @@ 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)');
}
cb(err);
return;
}
cb(null, obj);
});
};


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