UNPKG

deep-package-manager

Version:
706 lines (542 loc) 23.5 kB
/** * 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;