deep-package-manager
Version:
DEEP Package Manager
706 lines (542 loc) • 23.5 kB
JavaScript
/**
* Created by AlexanderC on 5/27/15.
*/
/*eslint max-statements: 0*/
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.LambdaService = undefined;
var _AbstractService = require('./AbstractService');
var _S3Service = require('./S3Service');
var _deepCore = require('deep-core');
var _deepCore2 = _interopRequireDefault(_deepCore);
var _AwsRequestSyncStack = require('../../Helpers/AwsRequestSyncStack');
var _Inflector = require('../../Helpers/Inflector');
var _FailedToCreateIamRoleException = require('./Exception/FailedToCreateIamRoleException');
var _FailedAttachingPolicyToRoleException = require('./Exception/FailedAttachingPolicyToRoleException');
var _Action = require('../../Microservice/Metadata/Action');
var _IAMService = require('./IAMService');
var _objectMerge = require('object-merge');
var _objectMerge2 = _interopRequireDefault(_objectMerge);
var _util = require('util');
var _CognitoIdentityService = require('./CognitoIdentityService');
var _CloudWatchLogsService = require('./CloudWatchLogsService');
var _CloudWatchEventsService = require('./CloudWatchEventsService');
var _SQSService = require('./SQSService');
var _ActionFlags = require('../../Microservice/Metadata/Helpers/ActionFlags');
var _ESService = require('./ESService');
var _FailedToCreateScheduledEventException = require('./Exception/FailedToCreateScheduledEventException');
var _FailedToAttachScheduledEventException = require('./Exception/FailedToAttachScheduledEventException');
var _CognitoIdentityProviderService = require('./CognitoIdentityProviderService');
var _SESService = require('./SESService');
var _APIGatewayService = require('./APIGatewayService');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* Lambda service
*/
class LambdaService extends _AbstractService.AbstractService {
/**
* @param {Array} args
*/
constructor(...args) {
super(...args);
this._deployedRoles = [];
}
/**
* @private
*/
_onConfigInject() {
this._deployedRoles = this._getDeployedRoles();
}
/**
* @returns {String[]}
* @private
*/
_getDeployedRoles() {
let deployedRoles = [];
if (this.isUpdate) {
let execRoles = this._config.executionRoles;
for (let microserviceIdentifier in execRoles) {
if (!execRoles.hasOwnProperty(microserviceIdentifier)) {
continue;
}
let execRole = execRoles[microserviceIdentifier];
deployedRoles.push(execRole.RoleName);
}
}
return deployedRoles;
}
/**
* @returns {String}
*/
name() {
return _deepCore2.default.AWS.Service.LAMBDA;
}
/**
* @returns {String[]}
*/
static get AVAILABLE_REGIONS() {
return [_deepCore2.default.AWS.Region.AP_NORTHEAST_TOKYO, _deepCore2.default.AWS.Region.AP_NORTHEAST_SEOUL, _deepCore2.default.AWS.Region.AP_SOUTHEAST_SYDNEY, _deepCore2.default.AWS.Region.AP_SOUTHEAST_SINGAPORE, _deepCore2.default.AWS.Region.AP_SOUTH_MUMBAI, _deepCore2.default.AWS.Region.EU_CENTRAL_FRANKFURT, _deepCore2.default.AWS.Region.EU_WEST_IRELAND, _deepCore2.default.AWS.Region.EU_WEST_LONDON, _deepCore2.default.AWS.Region.SA_EAST_SAO_PAULO, _deepCore2.default.AWS.Region.CA_CENTRAL_MONTREAL, _deepCore2.default.AWS.Region.US_EAST_VIRGINIA, _deepCore2.default.AWS.Region.US_EAST_OHIO, _deepCore2.default.AWS.Region.US_WEST_CALIFORNIA, _deepCore2.default.AWS.Region.US_WEST_OREGON];
}
/**
* @param {Core.Generic.ObjectStorage} services
* @returns {LambdaService}
*/
_setup(services) {
let microservices = this.provisioning.property.microservices;
this._createExecRoles(microservices)(execRoles => {
let lambdaNames = this._generateLambdasNames(microservices);
this._config.names = this.isUpdate ? (0, _objectMerge2.default)(this._config.names, lambdaNames) : lambdaNames;
this._config.executionRoles = this.isUpdate ? (0, _objectMerge2.default)(this._config.executionRoles, execRoles) : execRoles;
this._ready = true;
});
return this;
}
/**
* @param {Core.Generic.ObjectStorage} services
* @returns {LambdaService}
*/
_postProvision(services) {
let buckets = services.find(_S3Service.S3Service).config().buckets;
this._attachPolicyToExecRoles(buckets, this._config.executionRoles)(policies => {
this._config.executionRolesPolicies = this.isUpdate ? (0, _util._extend)(this._config.executionRolesPolicies, policies) : policies;
this._readyTeardown = true;
});
return this;
}
/**
* @param {Core.Generic.ObjectStorage} services
* @returns {LambdaService}
*/
_postDeployProvision(services) {
this._attachScheduledEvents(crons => {
this._config.crons = crons;
this._ready = true;
});
return this;
}
/**
* @param {Function} cb
* @private
*/
_attachScheduledEvents(cb) {
let microservices = this._provisioning.property.microservices;
let crons = {};
for (let microserviceKey in microservices) {
if (!microservices.hasOwnProperty(microserviceKey)) {
continue;
}
let microservice = microservices[microserviceKey];
let actions = microservice.resources.actions;
for (let actionKey in actions) {
if (!actions.hasOwnProperty(actionKey)) {
continue;
}
let action = actions[actionKey];
if (action.type === _Action.Action.LAMBDA && action.cron) {
let lambdaName = this.generateAwsResourceName(_Inflector.Inflector.pascalCase(action.identifier), _deepCore2.default.AWS.Service.LAMBDA, microservice.identifier);
crons[lambdaName] = {
cron: action.cron,
payload: action.cronPayload,
lambdaArn: this._generateLambdaArn(lambdaName),
eventArn: null
};
}
}
}
this._createScheduledEvents(crons).ready(() => {
cb(crons);
});
}
/**
* @param {Object} crons
* @returns {WaitFor}
* @private
*/
_createScheduledEvents(crons) {
let syncStack = new _AwsRequestSyncStack.AwsRequestSyncStack();
let cwe = this.provisioning.cloudWatchEvents;
let lambda = this.provisioning.lambda;
for (let lambdaName in crons) {
if (!crons.hasOwnProperty(lambdaName)) {
continue;
}
let cronData = crons[lambdaName];
let cronString = cronData.cron;
let lambdaArn = cronData.lambdaArn;
let payload = {
Name: lambdaName,
Description: `Schedule ${lambdaArn} (${cronString})`,
ScheduleExpression: `cron(${cronString})`,
State: 'ENABLED'
};
syncStack.push(cwe.putRule(payload), (error, data) => {
if (error) {
throw new _FailedToCreateScheduledEventException.FailedToCreateScheduledEventException(lambdaName, error);
}
crons[lambdaName].eventArn = data.RuleArn;
});
let permissionsPayload = {
Action: `${_deepCore2.default.AWS.Service.LAMBDA}:InvokeFunction`,
Principal: _deepCore2.default.AWS.Service.identifier(_deepCore2.default.AWS.Service.CLOUD_WATCH_EVENTS),
FunctionName: lambdaArn,
StatementId: lambdaName
};
syncStack.level(1).push(lambda.addPermission(permissionsPayload), error => {
if (error && error.code !== 'ResourceConflictException') {
throw new _FailedToAttachScheduledEventException.FailedToAttachScheduledEventException(lambdaName, error);
}
});
let targetPayload = {
Rule: lambdaName,
Targets: [{
Arn: lambdaArn,
Id: lambdaName
}]
};
if (cronData.payload) {
targetPayload.Targets[0].Input = JSON.stringify(cronData.payload);
}
syncStack.level(1).push(cwe.putTargets(targetPayload), error => {
if (error) {
throw new _FailedToAttachScheduledEventException.FailedToAttachScheduledEventException(lambdaName, error);
}
});
}
return syncStack.join();
}
/**
* @returns {Core.AWS.IAM.Policy}
*/
static getAssumeRolePolicy() {
let rolePolicy = new _deepCore2.default.AWS.IAM.Policy();
let statement = rolePolicy.statement.add();
statement.principal = {
Service: _deepCore2.default.AWS.Service.identifier(_deepCore2.default.AWS.Service.CLOUD_WATCH_EVENTS)
};
let action = statement.action.add();
action.service = _deepCore2.default.AWS.Service.LAMBDA;
action.action = 'InvokeFunction';
return rolePolicy;
}
/**
* @param {String} roleName
* @returns {Boolean}
* @private
*/
_isIamRoleNew(roleName) {
return this._deployedRoles.indexOf(roleName) === -1;
}
/**
* Creates execution roles for each lambda
*
* @param {Object} microservices
*
* @returns {Function}
* @private
*/
_createExecRoles(microservices) {
let iam = this.provisioning.iam;
let syncStack = new _AwsRequestSyncStack.AwsRequestSyncStack();
let execRoles = {};
// role policy (definition) is common for all lambdas
let execRolePolicy = _IAMService.IAMService.getAssumeRolePolicy(_deepCore2.default.AWS.Service.LAMBDA);
for (let microserviceKey in microservices) {
if (!microservices.hasOwnProperty(microserviceKey)) {
continue;
}
let microservice = microservices[microserviceKey];
let doUploadMicroserviceExecRole = microservice.resources.actions.reduce((isLambda, action) => {
return isLambda || action.type === _Action.Action.LAMBDA;
}, false);
if (doUploadMicroserviceExecRole) {
let roleName = this._generateLambdaRoleName(microservice.identifier);
let params = {
AssumeRolePolicyDocument: execRolePolicy.toString(),
RoleName: roleName
};
if (this._isIamRoleNew(roleName)) {
syncStack.push(iam.createRole(params), (error, data) => {
if (error) {
throw new _FailedToCreateIamRoleException.FailedToCreateIamRoleException(roleName, error);
}
execRoles[microservice.identifier] = data.Role;
});
}
}
}
return callback => {
return syncStack.join().ready(() => {
callback(execRoles);
});
};
}
/**
* @param {String} msIdentifier
* @returns {String}
* @private
*/
_generateLambdaRoleName(msIdentifier) {
return this.generateAwsResourceName(_Inflector.Inflector.pascalCase(msIdentifier) + 'LambdaExec', _deepCore2.default.AWS.Service.IDENTITY_AND_ACCESS_MANAGEMENT, msIdentifier);
}
/**
* @param {Object<Instance>} microservices
* @param {Function} filter
* @returns {Object}
* @private
*/
_generateLambdasNames(microservices, filter = () => true) {
let names = {};
for (let microserviceKey in microservices) {
if (!microservices.hasOwnProperty(microserviceKey)) {
continue;
}
let microservice = microservices[microserviceKey];
names[microservice.identifier] = {};
let actions = microservice.resources.actions.filter(filter);
for (let actionKey in actions) {
if (!actions.hasOwnProperty(actionKey)) {
continue;
}
let action = actions[actionKey];
if (action.type === _Action.Action.LAMBDA) {
names[microservice.identifier][action.identifier] = this.generateAwsResourceName(_Inflector.Inflector.pascalCase(action.identifier), _deepCore2.default.AWS.Service.LAMBDA, microservice.identifier);
}
}
}
return names;
}
/**
* Resolve DeepRN into ARN
*
* @example: @deep-account:user:create -> DeepDevUserCreate075234e258d
* @param {String} resourceName
* @returns {*}
*/
resolveDeepResourceName(resourceName) {
let parts = resourceName.match(/^@([^:]+):([^:]+):([^:]+)$/);
let names = this._config.names;
if (!parts) {
return null;
}
let microserviceIdentifier = parts[1];
let actionIdentifier = `${parts[2]}-${parts[3]}`;
let functionName = (names[microserviceIdentifier] || {})[actionIdentifier];
return functionName ? this._generateLambdaArn(functionName) : null;
}
/**
* Adds inline policies to lambdas execution roles
*
* @param {Array} buckets
* @param {String} roles
* @returns {*}
* @private
*/
_attachPolicyToExecRoles(buckets, roles) {
let iam = this.provisioning.iam;
let policies = {};
let syncStack = new _AwsRequestSyncStack.AwsRequestSyncStack();
let rootMicroservice = this.property.rootMicroservice;
for (let microserviceIdentifier in roles) {
if (!roles.hasOwnProperty(microserviceIdentifier)) {
continue;
}
let execRole = roles[microserviceIdentifier];
let policyName = this.generateAwsResourceName(_Inflector.Inflector.pascalCase(microserviceIdentifier) + 'LambdaExecPolicy', _deepCore2.default.AWS.Service.IDENTITY_AND_ACCESS_MANAGEMENT, microserviceIdentifier);
let policy = this._getAccessPolicy(microserviceIdentifier, buckets, microserviceIdentifier === rootMicroservice.identifier);
this.property.microservice(microserviceIdentifier).overwriteRolePolicy('lambda', policy);
let params = {
PolicyDocument: policy.toString(),
PolicyName: policyName,
RoleName: execRole.RoleName
};
syncStack.push(iam.putRolePolicy(params), (error, data) => {
if (error) {
throw new _FailedAttachingPolicyToRoleException.FailedAttachingPolicyToRoleException(policyName, execRole.RoleName, error);
}
policies[execRole.RoleName] = policy;
});
}
return callback => {
return syncStack.join().ready(() => {
callback(policies);
});
};
}
/**
* Allows lambda function access to all microservice resources (FS s3 buckets, DynamoDB tables, etc.)
*
* @param {String} microserviceIdentifier
* @param {Array} buckets
* @param {Boolean} rootLambda
* @param {String[]} dynamoDbLeadingKeys
*
* @returns {Policy}
*/
_getAccessPolicy(microserviceIdentifier, buckets, rootLambda = false, dynamoDbLeadingKeys = null) {
let policy = new _deepCore2.default.AWS.IAM.Policy();
let cloudWatchLogsService = this.provisioning.services.find(_CloudWatchLogsService.CloudWatchLogsService);
policy.statement.add(cloudWatchLogsService.generateAllowFullAccessStatement());
let cloudWatchEventsService = this.provisioning.services.find(_CloudWatchEventsService.CloudWatchEventsService);
policy.statement.add(cloudWatchEventsService.generateAllowEffectEventsRulesStatement());
// @todo: move it to DynamoDBService
let dynamoDbStatement = policy.statement.add();
dynamoDbStatement.action.add(_deepCore2.default.AWS.Service.DYNAMO_DB, _deepCore2.default.AWS.IAM.Policy.ANY);
dynamoDbStatement.resource.add(_deepCore2.default.AWS.Service.DYNAMO_DB, _deepCore2.default.AWS.IAM.Policy.ANY, this.awsAccountId, `table/${this._getGlobalResourceMask()}`);
// @todo: move it to S3Service
if (dynamoDbLeadingKeys) {
dynamoDbStatement.condition = {
'ForAllValues:StringEquals': {
'dynamodb:LeadingKeys': dynamoDbLeadingKeys
}
};
}
let s3Statement;
let s3ListBucketStatement;
let s3ReadBucketStatement;
if (Object.keys(buckets).length > 0) {
s3Statement = policy.statement.add();
s3ListBucketStatement = policy.statement.add();
s3ReadBucketStatement = policy.statement.add();
s3Statement.action.add(_deepCore2.default.AWS.Service.SIMPLE_STORAGE_SERVICE, _deepCore2.default.AWS.IAM.Policy.ANY);
s3ListBucketStatement.action.add(_deepCore2.default.AWS.Service.SIMPLE_STORAGE_SERVICE, 'ListBucket');
s3ReadBucketStatement.action.add(_deepCore2.default.AWS.Service.SIMPLE_STORAGE_SERVICE, 'GetObject');
s3ReadBucketStatement.action.add(_deepCore2.default.AWS.Service.SIMPLE_STORAGE_SERVICE, 'HeadObject');
}
for (let bucketSuffix in buckets) {
if (!buckets.hasOwnProperty(bucketSuffix)) {
continue;
}
let bucket = buckets[bucketSuffix];
if (bucketSuffix === _S3Service.S3Service.PUBLIC_BUCKET) {
let s3Resource = s3Statement.resource.add();
s3Resource.service = _deepCore2.default.AWS.Service.SIMPLE_STORAGE_SERVICE;
s3Resource.descriptor = rootLambda ? `${bucket.name}/${_deepCore2.default.AWS.IAM.Policy.ANY}` : `${bucket.name}/${microserviceIdentifier}/${_deepCore2.default.AWS.IAM.Policy.ANY}`;
} else {
let s3ResourceSystem = s3Statement.resource.add();
let s3ResourceTmp = s3Statement.resource.add();
let s3ResourceShared = s3Statement.resource.add();
s3ResourceSystem.service = _deepCore2.default.AWS.Service.SIMPLE_STORAGE_SERVICE;
s3ResourceSystem.descriptor = `${bucket.name}/${_S3Service.S3Service.PRIVATE_BUCKET}/` + `${microserviceIdentifier}/${_deepCore2.default.AWS.IAM.Policy.ANY}`;
s3ResourceTmp.service = _deepCore2.default.AWS.Service.SIMPLE_STORAGE_SERVICE;
s3ResourceTmp.descriptor = `${bucket.name}/${_S3Service.S3Service.TMP_BUCKET}/` + `${microserviceIdentifier}/${_deepCore2.default.AWS.IAM.Policy.ANY}`;
s3ResourceShared.service = _deepCore2.default.AWS.Service.SIMPLE_STORAGE_SERVICE;
s3ResourceShared.descriptor = `${bucket.name}/${_S3Service.S3Service.SHARED_BUCKET}/` + `${microserviceIdentifier}/${_deepCore2.default.AWS.IAM.Policy.ANY}`;
let s3ReadBucketResource = s3ReadBucketStatement.resource.add();
s3ReadBucketResource.service = _deepCore2.default.AWS.Service.SIMPLE_STORAGE_SERVICE;
s3ReadBucketResource.descriptor = `${bucket.name}/${_S3Service.S3Service.SHARED_BUCKET}/${_deepCore2.default.AWS.IAM.Policy.ANY}`;
}
let s3ListBucketResource = s3ListBucketStatement.resource.add();
s3ListBucketResource.service = _deepCore2.default.AWS.Service.SIMPLE_STORAGE_SERVICE;
s3ListBucketResource.descriptor = bucket.name;
}
let cognitoService = this.provisioning.services.find(_CognitoIdentityService.CognitoIdentityService);
policy.statement.add(cognitoService.generateAllowCognitoSyncStatement(['ListRecords', 'ListDatasets'], LambdaService));
policy.statement.add(cognitoService.generateAllowDescribeIdentityStatement(LambdaService));
policy.statement.add(this.generateAllowActionsStatement(['getFunctionConfiguration', 'InvokeFunction']));
let sqsService = this.provisioning.services.find(_SQSService.SQSService);
policy.statement.add(sqsService.generateAllowActionsStatement(['SendMessage', 'SendMessageBatch', 'ReceiveMessage', 'DeleteMessage', 'DeleteMessageBatch', 'GetQueueAttributes']));
let esService = this.provisioning.services.find(_ESService.ESService);
policy.statement.add(esService.generateAllowActionsStatement(['ESHttpGet', 'ESHttpHead', 'ESHttpDelete', 'ESHttpPost', 'ESHttpPut', 'DescribeElasticsearchDomain', 'DescribeElasticsearchDomains', 'ListDomainNames']));
let cognitoIdpService = this.provisioning.services.find(_CognitoIdentityProviderService.CognitoIdentityProviderService);
if (cognitoIdpService.isCognitoPoolEnabled) {
policy.statement.add(cognitoIdpService.generateAllowActionsStatement(['AdminUpdateUserAttributes', 'AdminInitiateAuth', 'AdminGetUser', 'AdminConfirmSignUp', 'AdminDeleteUser']));
}
// @todo: move it to ElastiCacheService?
let ec2Statement = policy.statement.add();
ec2Statement.action.add(_deepCore2.default.AWS.Service.EC2, 'CreateNetworkInterface');
ec2Statement.action.add(_deepCore2.default.AWS.Service.EC2, 'DescribeNetworkInterfaces');
ec2Statement.action.add(_deepCore2.default.AWS.Service.EC2, 'DeleteNetworkInterface');
ec2Statement.resource.add().any();
// @todo: move it to DynamoDBService?
let dynamoDbECStatement = policy.statement.add();
dynamoDbECStatement.action.add(_deepCore2.default.AWS.Service.CLOUD_WATCH, 'setAlarmState');
dynamoDbECStatement.resource.add().any();
let sesService = this.provisioning.services.find(_SESService.SESService);
policy.statement.add(sesService.generateAllowActionsStatement());
let apiGatewayService = this.provisioning.services.find(_APIGatewayService.APIGatewayService);
policy.statement.add(apiGatewayService.manageApiGenerateAllowActionsStatement(['*']));
if (this._allowAlterIamService(microserviceIdentifier)) {
let iamService = this.provisioning.services.find(_IAMService.IAMService);
policy.statement.add(iamService.generateAllowAlterIamStatement());
}
let xRayStatement = policy.statement.add();
xRayStatement.action.add(_deepCore2.default.AWS.Service.X_RAY, 'PutTraceSegments');
xRayStatement.action.add(_deepCore2.default.AWS.Service.X_RAY, 'PutTelemetryRecords');
xRayStatement.resource.add().any();
return policy;
}
/**
* @param {String} functionIdentifier
* @returns {String}
*/
_generateLambdaArn(functionIdentifier) {
let region = this.provisioning.lambda.config.region;
return `arn:aws:lambda:${region}:${this.awsAccountId}:function:${functionIdentifier}`;
}
/**
* @param {String} microserviceIdentifier
* @returns {Boolean}
* @private
*/
_allowAlterIamService(microserviceIdentifier) {
let accountMicroservice = this.provisioning.property.accountMicroservice;
return accountMicroservice && accountMicroservice.identifier === microserviceIdentifier;
}
/**
* @param {Object[]} actions
* @returns {Core.AWS.IAM.Statement}
*/
generateAllowActionsStatement(actions = ['InvokeFunction']) {
let policy = new _deepCore2.default.AWS.IAM.Policy();
let statement = policy.statement.add();
actions.forEach(actionName => {
statement.action.add(_deepCore2.default.AWS.Service.LAMBDA, actionName);
});
statement.resource.add().updateFromArn(this._generateLambdaArn(this._getGlobalResourceMask()));
return statement;
}
/**
* @param {Function} filter
* @returns {String[]}
*/
extractFunctionIdentifiers(filter = () => true) {
let lambdaIdentifiers = [];
let privateLambdasObj = this._generateLambdasNames(this._provisioning.property.microservices, filter);
for (let k in privateLambdasObj) {
if (!privateLambdasObj.hasOwnProperty(k)) {
continue;
}
let privateLambdasObjNested = privateLambdasObj[k];
for (let nk in privateLambdasObjNested) {
if (!privateLambdasObjNested.hasOwnProperty(nk)) {
continue;
}
lambdaIdentifiers.push(privateLambdasObjNested[nk]);
}
}
return lambdaIdentifiers;
}
/**
* Deny Cognito and ApiGateway users to invoke these lambdas
*
* @param {Function} filter
* @returns {Core.AWS.IAM.Statement|null}
*/
generateDenyInvokeFunctionStatement(filter = _ActionFlags.ActionFlags.NON_DIRECT_ACTION_FILTER) {
let policy = new _deepCore2.default.AWS.IAM.Policy();
let statement = policy.statement.add();
statement.effect = statement.constructor.DENY;
statement.action.add(_deepCore2.default.AWS.Service.LAMBDA, 'InvokeFunction');
let lambdaArns = this.extractFunctionIdentifiers(filter);
if (lambdaArns.length <= 0) {
return null;
}
lambdaArns.forEach(lambdaArn => {
statement.resource.add().updateFromArn(this._generateLambdaArn(lambdaArn));
});
return statement;
}
}
exports.LambdaService = LambdaService;