Skip to content

Commit

Permalink
Go big or go home an refactor completely for param store (#50)
Browse files Browse the repository at this point in the history
* go big or go home an refactor completely for param store

* remove s3 collector functionality

* address comments

* fix tests and cfn template
  • Loading branch information
JDragovichAlertLogic authored and kkuzmin committed Jan 27, 2020
1 parent c8a2ba0 commit 6286813
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 186 deletions.
68 changes: 45 additions & 23 deletions cfn/paws-collector.template
Original file line number Diff line number Diff line change
Expand Up @@ -79,17 +79,21 @@
"Type": "String",
"NoEcho": true
},
"PawsSecretParamTier": {
"Description": "Tier for AWS Param store param. Change this to advanced if the size of the PAws secrets is over 4kb",
"Type": "String",
"Default": "Standard",
"AllowedValues": [
"Standard",
"Advanced",
"Intelligent-Tiering"
]
},
"PawsMaxPagesPerInvocation" :{
"Description": "The maximum number of pages of content data retrieved per invocation",
"Type": "Number",
"Default": 10
},
"PawsCredsS3Uri": {
"Description": "The S3 uri where object where credentials are stored. in the for s3://<bucket>/<object-path> Only used when PawsAuthType is 's3object'",
"Type": "String",
"AllowedPattern" : "^s3:\/\/[a-z,\-,0-9]*\/[a-z,\-,\.,\_,0-9]*",
"Default": ""
},
"CollectionStartTs": {
"Description": "Optional. Timestamp when log collection starts. For example, 2020-01-13T16:00:00Z.",
"Type": "String",
Expand Down Expand Up @@ -368,11 +372,10 @@
"\n",
"exports.handler = (event, context, callback) => {\n",
" if (event.ResourceType == 'AWS::CloudFormation::CustomResource' &&\n",
" event.RequestType == 'Create' &&\n",
" event.ResourceProperties.Plaintext.length > 0) {\n",
" event.RequestType == 'Create') {\n",
" return encrypt(event, context);\n",
" }\n",
" return response.send(event, context, response.SUCCESS, {EncryptedText: \"\"});\n",
" return response.send(event, context, response.SUCCESS);\n",
"}"
]
]
Expand Down Expand Up @@ -440,6 +443,28 @@
}
}
},
"PawsSecretParam": {
"Type": "AWS::SSM::Parameter",
"Properties": {
"Name": {"Fn::Join": [
"",
[
"PAWS-SECRET-",
{"Ref": "PawsCollectorTypeName"},
"-",
{"Ref": "AWS::StackName"}
]
]},
"Type": "String",
"Tier": {"Ref": "PawsSecretParamTier"},
"Value": {
"Fn::GetAtt": ["EncryptPawsSecret", "EncryptedText"]
},
"Tags": {
"StackId": {"Ref": "AWS::StackId"}
}
}
},
"PawsPollStateQueue":{
"Type":"AWS::SQS::Queue",
"DependsOn":[
Expand Down Expand Up @@ -554,11 +579,8 @@
"paws_kms_key_arn":{
"Fn::GetAtt": [ "LambdaKmsKey", "Arn" ]
},
"paws_api_secret":{
"Fn::GetAtt": ["EncryptPawsSecret", "EncryptedText"]
},
"paws_s3_object_path":{
"Ref": "PawsCredsS3Uri"
"paws_secret_param_name":{
"Ref":"PawsSecretParam"
},
"paws_collection_start_ts":{
"Ref":"CollectionStartTs"
Expand Down Expand Up @@ -633,16 +655,16 @@
},
{
"Effect":"Allow",
"Action":"s3:Get*",
"Action":"ssm:GetParameter",
"Resource":[
{
"Fn::Join":["",
[
"arn:aws:s3:::",
{ "Fn::Select": ["1", {"Fn::Split": ["//", {"Ref": "PawsCredsS3Uri"}] } ] }
]
]

{ "Fn::Join":["", [
"arn:aws:ssm:",
{"Ref": "AWS::Region"},
":",
{"Ref": "AWS::AccountId"},
":parameter/",
{"Ref":"PawsSecretParam"}
]]
}
]
},
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@alertlogic/paws-collector",
"version": "1.0.11",
"version": "1.0.12",
"license": "MIT",
"description": "Alert Logic AWS based API Poll Log Collector Library",
"repository": {
Expand Down
112 changes: 35 additions & 77 deletions paws_collector.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,93 +21,52 @@ const m_packageJson = require('./package.json');
const CREDS_FILE_PATH = '/tmp/paws_creds.json';
var PAWS_DECRYPTED_CREDS = null;

function getPawsCredsFile(){
function getPawsParamStoreParam(){
return new Promise((resolve, reject) => {
// check if the creds file is cached and retrieve it if it is not
if(!fs.existsSync(CREDS_FILE_PATH)){
const s3 = new AWS.S3({apiVersion: '2006-03-01'});
const kms = new AWS.KMS();

// doing the string manipulation here because doing it here is way less groos than doing it in the cfn
const s3Path = process.env.paws_s3_object_path.split('//')[1];
const s3PathParts = s3Path.split('/');

// retrive the object from S3
var params = {
Bucket: s3PathParts.shift(),
Key: s3PathParts.join('/')
};
s3.getObject(params, (err, data) => {
if (err) return reject(new Error(err, err.stack));
var ssm = new AWS.SSM();
var params = {
Name: process.env.paws_secret_param_name
};
ssm.getParameter(params, function(err, {Parameter:{Value}}) {
if (err) reject(err, err.stack);
else resolve(Buffer.from(Value, 'base64'));
});
});
}

// encrypt the file contents and cache on the lambda container file system
const encryptParams ={
Plaintext: data.Body,
KeyId: process.env.paws_kms_key_arn
};
kms.encrypt(encryptParams, (encryptError, encryptResponse) => {
if (encryptError) return reject(new Error(encryptError, encryptError.stack));
function getDecryptedPawsCredentials(credsBuffer) {
return new Promise((resolve, reject) => {
if (PAWS_DECRYPTED_CREDS) {
return resolve(PAWS_DECRYPTED_CREDS);
} else {
const kms = new AWS.KMS();
kms.decrypt(
{CiphertextBlob: credsBuffer},
(err, data) => {
if (err) {
return reject(err);
} else {
PAWS_DECRYPTED_CREDS = {
auth_type: process.env.paws_api_auth_type,
client_id: process.env.paws_api_client_id,
secret: data.Plaintext.toString('ascii')
};

fs.writeFileSync(CREDS_FILE_PATH, encryptResponse.CiphertextBlob);
return resolve(encryptResponse.CiphertextBlob);
})
});
}
else {
return resolve(fs.readFileSync(CREDS_FILE_PATH));
return resolve(PAWS_DECRYPTED_CREDS);
}
});
}
});
};

function getDecryptedPawsCredentials(credsBuffer, callback) {
if (PAWS_DECRYPTED_CREDS) {
return callback(null, PAWS_DECRYPTED_CREDS);
} else {
const kms = new AWS.KMS();
kms.decrypt(
{CiphertextBlob: credsBuffer},
(err, data) => {
if (err) {
return callback(err);
} else {
PAWS_DECRYPTED_CREDS = {
auth_type: process.env.paws_api_auth_type,
client_id: process.env.paws_api_client_id,
secret: data.Plaintext.toString('ascii')
};

return callback(null, PAWS_DECRYPTED_CREDS);
}
});
}
}

class PawsCollector extends AlAwsCollector {

static load() {
return new Promise(function(resolve, reject){
AlAwsCollector.load().then(function(aimsCreds) {
let credsPromise;

switch(process.env.paws_auth_type){
case 's3object':
credsPromise = getPawsCredsFile();
break;
default:
const enVarCreds = Buffer.from(process.env.paws_api_secret, 'base64');
credsPromise = new Promise(res => res(enVarCreds));
}
return AlAwsCollector.load().then(function(aimsCreds) {

return credsPromise.then(credsBuffer => {
getDecryptedPawsCredentials(credsBuffer, function(err, pawsCreds) {
if (err){
reject(err);
} else {
resolve({aimsCreds : aimsCreds, pawsCreds: pawsCreds});
}
});
}).catch(err => console.error(`PAWS000400 Error getting Paws Credentials ${err}`));
});
return getPawsParamStoreParam()
.then(getDecryptedPawsCredentials)
.then(pawsCreds => ({aimsCreds, pawsCreds}));
});
}

Expand Down Expand Up @@ -307,7 +266,6 @@ class PawsCollector extends AlAwsCollector {
}

module.exports = {
getPawsCredsFile,
PawsCollector: PawsCollector
}

102 changes: 17 additions & 85 deletions test/paws_test.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
const assert = require('assert');
const rewire = require('rewire');
const sinon = require('sinon');
var AWS = require('aws-sdk-mock');
const fs = require('fs');
const m_response = require('cfn-response');

const pawsMock = require('./paws_mock');
Expand Down Expand Up @@ -153,22 +151,13 @@ class TestCollectorMultiState extends PawsCollector {
}

describe('Unit Tests', function() {
let loadStub;

beforeEach(function(){
AWS.mock('KMS', 'decrypt', function (params, callback) {
let data;
if(params.CiphertextBlob.toString() === 'creds-from-file'){
console.log('dcrypting file');
data = {
Plaintext : 'decrypted-secret-key-from-file'
};
}
else{
console.log('decrypting somthing else');
data = {
const data = {
Plaintext : 'decrypted-sercret-key'
};
}
return callback(null, data);
});

Expand All @@ -179,11 +168,20 @@ describe('Unit Tests', function() {
return callback(null, data);
});

AWS.mock('S3', 'getObject', function (params, callback) {
const data = {
Body: Buffer.from('creds-from-file')
};
return callback(null, data);
loadStub = sinon.stub(PawsCollector, 'load').callsFake(() => {
return new Promise(res => {
return res({
aimsCreds:{
access_key_id: 'access_key_id',
secret_key: 'secret_key'
},
pawsCreds: {
auth_type: 'auth_type',
client_id: 'client_id',
secret: 'secret'
}
});
});
});

responseStub = sinon.stub(m_response, 'send').callsFake(
Expand All @@ -202,73 +200,7 @@ describe('Unit Tests', function() {
restoreAlServiceStub();
setEnvStub.restore();
responseStub.restore();
});

describe('Load function', function() {
const rewiredTestModule = rewire('../paws_collector');
const {PawsCollector: LoadTestCollector} = rewiredTestModule;
let fileWriteStub;
let fileReadStub;
let fileExistsStub;
const CREDS_FILE_PATH = '/tmp/paws_creds.json';

beforeEach(() => {
rewiredTestModule.__set__("PAWS_DECRYPTED_CREDS", null);
fileWriteStub = sinon.spy(fs, 'writeFileSync');
fileReadStub = sinon.spy(fs, 'readFileSync');
});

afterEach(() => {
fileWriteStub.restore();
fileReadStub.restore();
});

it('gets creds from s3 if present and writes them to cache', function(done){
const oldAuthType = process.env.paws_auth_type;
process.env.paws_auth_type = 's3object';
fileExistsStub = sinon.stub(fs, 'existsSync').callsFake(() => {
return false;
});

LoadTestCollector.load().then(function({pawsCreds}){
assert.equal(pawsCreds.secret, 'decrypted-secret-key-from-file');
assert.ok(fileReadStub.called);
assert.ok(fileWriteStub.calledWith(CREDS_FILE_PATH));
process.env.paws_auth_type = oldAuthType;
fileExistsStub.restore();
done();
});
});

it('gets creds from cache', function(done){
const oldAuthType = process.env.paws_auth_type;
process.env.paws_auth_type = 's3object';
fs.writeFileSync(CREDS_FILE_PATH, 'creds-from-file');
fileWriteStub.resetHistory();
fileExistsStub = sinon.stub(fs, 'existsSync').callsFake(() => {
return true;
});

LoadTestCollector.load().then(function({pawsCreds}){
assert.equal(pawsCreds.secret, 'decrypted-secret-key-from-file');
assert.equal(fileReadStub.calledWith(CREDS_FILE_PATH), true);
assert.equal(fileWriteStub.calledWith(CREDS_FILE_PATH), false);
process.env.paws_auth_type = oldAuthType;
fs.unlinkSync(CREDS_FILE_PATH);
fileExistsStub.restore();
done();
});
});

it('does not get the creds from a file when the auth type is not s3object', function(done){
LoadTestCollector.load().then(function({pawsCreds}){
assert.notEqual(pawsCreds.secret, 'decrypted-secret-key-from-file');
assert.equal(fileWriteStub.called, false);
assert.equal(fileReadStub.called, false);
fileExistsStub.restore();
done();
});
});
loadStub.restore();
});

describe('Poll Request Tests', function() {
Expand Down

0 comments on commit 6286813

Please sign in to comment.