Skip to content

Instantly share code, notes, and snippets.

@issacg
Created November 8, 2017 12:35
Show Gist options
  • Save issacg/ea661c6652d00191d1d6f08fc9b38b60 to your computer and use it in GitHub Desktop.
Save issacg/ea661c6652d00191d1d6f08fc9b38b60 to your computer and use it in GitHub Desktop.
Reference code for unsealing a Vault instance using Authy OneTouch push notifications
{
"swagger": "2.0",
"info": {
"version": "2017-11-05T23:36:16Z",
"title": "Vault Unseal API"
},
"host": "myproject.execute-api.us-east-1.amazonaws.com",
"basePath": "/v1",
"schemes": [
"https"
],
"paths": {
"/cb": {
"post": {
"produces": [
"application/json"
],
"responses": {
"200": {
"description": "200 response",
"schema": {
"$ref": "#/definitions/Empty"
}
}
},
"x-amazon-apigateway-integration": {
"responses": {
"default": {
"statusCode": "200"
}
},
"uri": "arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-2:<account>:function:script2/invocations",
"passthroughBehavior": "when_no_match",
"httpMethod": "POST",
"contentHandling": "CONVERT_TO_TEXT",
"type": "aws_proxy"
}
}
},
"/start": {
"get": {
"produces": [
"application/json"
],
"responses": {
"200": {
"description": "200 response",
"schema": {
"$ref": "#/definitions/Empty"
}
}
},
"x-amazon-apigateway-integration": {
"responses": {
"default": {
"statusCode": "200"
}
},
"uri": "arn:aws:apigateway:us-east-2:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-2:<account>:function:script1/invocations",
"passthroughBehavior": "when_no_match",
"httpMethod": "POST",
"contentHandling": "CONVERT_TO_TEXT",
"type": "aws_proxy"
}
}
}
},
"definitions": {
"Empty": {
"type": "object",
"title": "Empty Schema"
}
}
}
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"kms:Decrypt"
],
"Resource": "arn:aws:kms:us-east-1:AWS-ACCOUNT:key/KMS-KEY-GUID"
}
]
}
{
"name": "unsealvault",
"version": "1.0.0",
"description": "AWS Lambda functions for unsealing Vault",
"main": "main.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Issac Goldstand <[email protected]>",
"license": "Apache-2.0",
"dependencies": {
"authy-client": "^1.0.10",
"request": "^2.83.0",
"request-promise-native": "^1.0.5"
}
}
// Copyright 2017 Issac Goldstand <[email protected]>
/*
* This script will send an Authy OneTouch authorization request to unseal the vault
* Although in it's current form, the variables are hard-coded, it can be easily
* customized to be more dynamic.
* The script is written to be called as an AWS Lambda function via the AWS API Gateway proxy service
*/
var authy = new (require('authy-client').Client)({key: "YOURKEY"}); // Set to your Authy API key and can be found in the Authy application dashboard
var blob = "SOMEBASE64DATA="; // This is the base64 string representing the KMS-encoded unseal key. To encrypt the value, see https://docs.aws.amazon.com/cli/latest/reference/kms/encrypt.html
var vault_url = "https://vault.your-company.com:8200"; // This is the base URL of the Vault instance to be unsealed. Ensure that it's accessible from the callback script!
var authy_user_id = 123456; // This is the user ID of the Authy user to get the push notification and can be found in the Authy application dashboard
exports.handler = (event, context, callback) => {
var response;
Promise.resolve()
.then(() => {
if (!(event && event.headers && event.headers['User-Agent']))
throw `Missing UserAgent ${JSON.stringify(event.headers)}`;
if (!(event && event.headers && event.headers['X-Forwarded-For']))
throw `Missing IP ${JSON.stringify(event.headers)}`;
return authy.createApprovalRequest({
authyId: authy_user_id,
message: `Request from IP/chain ${event.headers['X-Forwarded-For']} to unseal Vault at ${vault_url}`,
details: {
visible: {
UserAgent: event.headers['User-Agent'],
IP: event.headers['X-Forwarded-For']
},
hidden: {
vault_url: vault_url,
blob: blob
}
},
options: {
ttl: 120
}
});
}).then((res) => {
response = {
statusCode: 200,
body: JSON.stringify({status: "OK", request_id: res.approval_request.uuid})
};
callback(null, response);
}).catch((e) => {
response = {
statusCode: 500,
body: JSON.stringify({status:"ERROR", error: e.stack ? e.stack : e.toString()})
};
callback(null, response);
});
};
// Copyright 2017 Issac Goldstand <[email protected]>
/*
* This script will validate an authorization request from the Authy OneTouch API
* and use the parameters in that request to unseal a Vault instance.
* The script is written to be called as an AWS Lambda function via the AWS API Gateway proxy service.
* In addition to the standard permissions, this script requires permission to decrypt with the
* KMS key used to encrypt the key.
*
* An example policy is attached to this gist
*
* The script is written to be called as an AWS Lambda function via the AWS API Gateway proxy service
*/
var authy = new (require('authy-client').Client)({key: "YOURKEY"}); // Set to your Authy API key and can be found in the Authy application dashboard
var request = require('request-promise-native');
var AWS = require('aws-sdk');
AWS.config.update({region: 'us-east-2'}); // Set to the AWS region that your KMS key exists in
exports.handler = (event, context, callback) => {
var response;
Promise.resolve()
.then(() => {
if (!(event && event.headers && event.headers['X-Forwarded-Proto']))
throw `Missing X-Forwarded-Proto ${JSON.stringify(event.headers)}`;
if (!(event && event.requestContext && event.requestContext.path))
throw `Missing URL info ${JSON.stringify(event.requestContext)}`;
event.body = JSON.parse(event.body);
// Massage request for authy-client so that it doesn't fail the signature
// Host header should be lower-case
event.headers.host =
(event.headers.host ? event.headers.host :
(event.headers.Host ? event.headers.Host :
// we're screwed - we didn't get a host header!
''));
// device_uuid must be a string, or authy-client will fail validation
event.body.device_uuid = event.body.device_uuid.toString();
var obj = {
body: event.body,
headers: event.headers,
method: event.httpMethod,
protocol: event.headers["X-Forwarded-Proto"],
url: event.requestContext.path
};
return authy.verifyCallback(obj);
}).then(() => {
if (event.body.status != "approved") throw `Request ${event.body.uuid} was ${event.body.status}. ${event.body.approval_request.transaction.reason}`;
console.log(`Request approved. Fetching unseal key...`);
var KMS = new AWS.KMS();
var blob = event.body.approval_request.transaction.hidden_details.blob;
return KMS.decrypt({CiphertextBlob: Buffer.from(blob, 'base64')}).promise();
}).then((res) => {
var buf = Buffer(res.Plaintext).toString();
var sub = buf.substring(0,3);
var url = event.body.approval_request.transaction.hidden_details.vault_url;
console.log(`Unsealing vault at ${url} with key ${sub}...`);
return request({
url: `${url}/v1/sys/unseal`,
method: "PUT",
body: {key: buf},
json: true,
strictSSL: false
});
}).then((res) => {
response = {
statusCode: 200,
headers: {},
body: JSON.stringify(res)
};
callback(null, response);
}).catch((e) => {
var err = e.stack ? e.stack : e.toString();
console.error(err);
if (e.errors) console.log(JSON.stringify(e.errors));
response = {
statusCode: 500,
body: JSON.stringify({status:"ERROR", error: err})
};
callback(null, response);
});
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment