Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge upstream #2

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
24 changes: 13 additions & 11 deletions lib/classes/Utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const isDockerContainer = require('is-docker');
const version = require('../../package.json').version;
const segment = require('../utils/segment');
const configUtils = require('../utils/config');
const awsArnRegExs = require('../plugins/aws/utils/arnRegularExpressions');

class Utils {
constructor(serverless) {
Expand Down Expand Up @@ -206,9 +207,9 @@ class Utils {
// For HTTP events, see what authorizer types are enabled
if (event.http && event.http.authorizer) {
if ((typeof event.http.authorizer === 'string'
&& event.http.authorizer.toUpperCase() === 'AWS_IAM')
|| (event.http.authorizer.type
&& event.http.authorizer.type.toUpperCase() === 'AWS_IAM')) {
&& event.http.authorizer.toUpperCase() === 'AWS_IAM')
|| (event.http.authorizer.type
&& event.http.authorizer.type.toUpperCase() === 'AWS_IAM')) {
hasIAMAuthorizer = true;
}
// There are three ways a user can specify a Custom authorizer:
Expand All @@ -217,18 +218,19 @@ class Utils {
// 2) By listing the name of a function in the same service for the name property
// in the authorizer object.
// 3) By listing a function's ARN in the arn property of the authorizer object.

if ((typeof event.http.authorizer === 'string'
&& event.http.authorizer.toUpperCase() !== 'AWS_IAM'
&& !event.http.authorizer.includes('arn:aws:cognito-idp'))
|| event.http.authorizer.name
|| (event.http.authorizer.arn
&& event.http.authorizer.arn.includes('arn:aws:lambda'))) {
&& event.http.authorizer.toUpperCase() !== 'AWS_IAM'
&& !awsArnRegExs.cognitoIdpArnExpr.test(event.http.authorizer))
|| event.http.authorizer.name
|| (event.http.authorizer.arn
&& awsArnRegExs.lambdaArnExpr.test(event.http.authorizer.arn))) {
hasCustomAuthorizer = true;
}
if ((typeof event.http.authorizer === 'string'
&& event.http.authorizer.includes('arn:aws:cognito-idp'))
|| (event.http.authorizer.arn
&& event.http.authorizer.arn.includes('arn:aws:cognito-idp'))) {
&& awsArnRegExs.cognitoIdpArnExpr.test(event.http.authorizer))
|| (event.http.authorizer.arn
&& awsArnRegExs.cognitoIdpArnExpr.test(event.http.authorizer.arn))) {
hasCognitoAuthorizer = true;
}
}
Expand Down
11 changes: 11 additions & 0 deletions lib/plugins/aws/deploy/lib/getS3EndpointForRegion.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
'use strict';

module.exports = function getS3EndpointForRegion(region) {
const strRegion = region.toLowerCase();
// look for govcloud - currently s3-us-gov-west-1.amazonaws.com
if (strRegion.match(/us-gov/)) return `s3-${strRegion}.amazonaws.com`;
// look for china - currently s3.cn-north-1.amazonaws.com.cn
if (strRegion.match(/cn-/)) return `s3.${strRegion}.amazonaws.com.cn`;
// default s3 endpoint for other regions
return 's3.amazonaws.com';
};
21 changes: 21 additions & 0 deletions lib/plugins/aws/deploy/lib/getS3EndpointForRegion.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
'use strict';
const expect = require('chai').expect;
const getS3EndpointForRegion = require('./getS3EndpointForRegion');

describe('getS3EndpointForRegion', () => {
it('should return standard endpoint for us-east-1', () => {
const expected = 's3.amazonaws.com';
const actual = getS3EndpointForRegion('us-east-1');
expect(actual).to.equal(expected);
});
it('should return govcloud endpoint for us-gov-west-1', () => {
const expected = 's3-us-gov-west-1.amazonaws.com';
const actual = getS3EndpointForRegion('us-gov-west-1');
expect(actual).to.equal(expected);
});
it('should return china endpoint for cn-north-1', () => {
const expected = 's3.cn-north-1.amazonaws.com.cn';
const actual = getS3EndpointForRegion('cn-north-1');
expect(actual).to.equal(expected);
});
});
5 changes: 3 additions & 2 deletions lib/plugins/aws/deploy/lib/validateTemplate.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
'use strict';
const getS3EndpointForRegion = require('./getS3EndpointForRegion');

module.exports = {
validateTemplate() {
const bucketName = this.bucketName;
const artifactDirectoryName = this.serverless.service.package.artifactDirectoryName;
const compiledTemplateFileName = 'compiled-cloudformation-template.json';

const s3Endpoint = getS3EndpointForRegion(this.provider.getRegion());
this.serverless.cli.log('Validating template...');
const params = {
TemplateURL: `https://s3.amazonaws.com/${bucketName}/${artifactDirectoryName}/${compiledTemplateFileName}`,
TemplateURL: `https://${s3Endpoint}/${bucketName}/${artifactDirectoryName}/${compiledTemplateFileName}`,
};

return this.provider.request(
Expand Down
30 changes: 15 additions & 15 deletions lib/plugins/aws/deployFunction/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,19 +57,19 @@ class AwsDeployFunction {
'getFunction',
params
)
.then((result) => {
this.serverless.service.provider.remoteFunctionData = result;
return result;
})
.catch(() => {
const errorMessage = [
`The function "${this.options.function}" you want to update is not yet deployed.`,
' Please run "serverless deploy" to deploy your service.',
' After that you can redeploy your services functions with the',
' "serverless deploy function" command.',
].join('');
throw new this.serverless.classes.Error(errorMessage);
});
.then((result) => {
this.serverless.service.provider.remoteFunctionData = result;
return result;
})
.catch(() => {
const errorMessage = [
`The function "${this.options.function}" you want to update is not yet deployed.`,
' Please run "serverless deploy" to deploy your service.',
' After that you can redeploy your services functions with the',
' "serverless deploy function" command.',
].join('');
throw new this.serverless.classes.Error(errorMessage);
});
}

normalizeArnRole(role) {
Expand All @@ -84,8 +84,8 @@ class AwsDeployFunction {
const roleProperties = roleResource.Properties;
const compiledFullRoleName = `${roleProperties.Path || '/'}${roleProperties.RoleName}`;

return this.provider.getAccountId().then((accountId) =>
`arn:aws:iam::${accountId}:role${compiledFullRoleName}`
return this.provider.getAccountInfo().then((result) =>
`arn:${result.partition}:iam::${result.accountId}:role${compiledFullRoleName}`
);
}

Expand Down
18 changes: 9 additions & 9 deletions lib/plugins/aws/deployFunction/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,13 +120,13 @@ describe('AwsDeployFunction', () => {
});

describe('#normalizeArnRole', () => {
let getAccountIdStub;
let getAccountInfoStub;
let getRoleStub;

beforeEach(() => {
getAccountIdStub = sinon
.stub(awsDeployFunction.provider, 'getAccountId')
.resolves('123456789012');
getAccountInfoStub = sinon
.stub(awsDeployFunction.provider, 'getAccountInfo')
.resolves({ accountId: '123456789012', partition: 'aws' });
getRoleStub = sinon
.stub(awsDeployFunction.provider, 'request')
.resolves({ Arn: 'arn:aws:iam::123456789012:role/role_2' });
Expand All @@ -144,7 +144,7 @@ describe('AwsDeployFunction', () => {
});

afterEach(() => {
awsDeployFunction.provider.getAccountId.restore();
awsDeployFunction.provider.getAccountInfo.restore();
awsDeployFunction.provider.request.restore();
serverless.service.resources = undefined;
});
Expand All @@ -153,7 +153,7 @@ describe('AwsDeployFunction', () => {
const arn = 'arn:aws:iam::123456789012:role/role';

return awsDeployFunction.normalizeArnRole(arn).then((result) => {
expect(getAccountIdStub.calledOnce).to.be.equal(false);
expect(getAccountInfoStub.calledOnce).to.be.equal(false);
expect(result).to.be.equal(arn);
});
});
Expand All @@ -162,7 +162,7 @@ describe('AwsDeployFunction', () => {
const roleName = 'MyCustomRole';

return awsDeployFunction.normalizeArnRole(roleName).then((result) => {
expect(getAccountIdStub.calledOnce).to.be.equal(true);
expect(getAccountInfoStub.calledOnce).to.be.equal(true);
expect(result).to.be.equal('arn:aws:iam::123456789012:role/role_123');
});
});
Expand All @@ -177,7 +177,7 @@ describe('AwsDeployFunction', () => {

return awsDeployFunction.normalizeArnRole(roleObj).then((result) => {
expect(getRoleStub.calledOnce).to.be.equal(true);
expect(getAccountIdStub.calledOnce).to.be.equal(false);
expect(getAccountInfoStub.calledOnce).to.be.equal(false);
expect(result).to.be.equal('arn:aws:iam::123456789012:role/role_2');
});
});
Expand Down Expand Up @@ -336,7 +336,7 @@ describe('AwsDeployFunction', () => {
awsDeployFunction.options = options;

return expect(awsDeployFunction.updateFunctionConfiguration()).to.be.fulfilled
.then(() => expect(updateFunctionConfigurationStub).to.not.be.called);
.then(() => expect(updateFunctionConfigurationStub).to.not.be.called);
});

it('should fail when using invalid characters in environment variable', () => {
Expand Down
9 changes: 6 additions & 3 deletions lib/plugins/aws/lib/updateStack.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

const _ = require('lodash');
const BbPromise = require('bluebird');
const getS3EndpointForRegion = require('../deploy/lib/getS3EndpointForRegion');

const NO_UPDATE_MESSAGE = 'No updates are to be performed.';

Expand All @@ -13,7 +14,8 @@ module.exports = {
const stackName = this.provider.naming.getStackName();
let stackTags = { STAGE: this.provider.getStage() };
const compiledTemplateFileName = 'compiled-cloudformation-template.json';
const templateUrl = `https://s3.amazonaws.com/${this.bucketName}/${this.serverless.service.package.artifactDirectoryName}/${compiledTemplateFileName}`;
const s3Endpoint = getS3EndpointForRegion(this.provider.getRegion());
const templateUrl = `https://${s3Endpoint}/${this.bucketName}/${this.serverless.service.package.artifactDirectoryName}/${compiledTemplateFileName}`;

// Merge additional stack tags
if (typeof this.serverless.service.provider.stackTags === 'object') {
Expand Down Expand Up @@ -44,7 +46,8 @@ module.exports = {

update() {
const compiledTemplateFileName = 'compiled-cloudformation-template.json';
const templateUrl = `https://s3.amazonaws.com/${this.bucketName}/${this.serverless.service.package.artifactDirectoryName}/${compiledTemplateFileName}`;
const s3Endpoint = getS3EndpointForRegion(this.provider.getRegion());
const templateUrl = `https://${s3Endpoint}/${this.bucketName}/${this.serverless.service.package.artifactDirectoryName}/${compiledTemplateFileName}`;

this.serverless.cli.log('Updating Stack...');
const stackName = this.provider.naming.getStackName();
Expand Down Expand Up @@ -72,7 +75,7 @@ module.exports = {

// Policy must have at least one statement, otherwise no updates would be possible at all
if (this.serverless.service.provider.stackPolicy &&
this.serverless.service.provider.stackPolicy.length) {
this.serverless.service.provider.stackPolicy.length) {
params.StackPolicyBody = JSON.stringify({
Statement: this.serverless.service.provider.stackPolicy,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

const BbPromise = require('bluebird');
const _ = require('lodash');
const awsArnRegExs = require('../../../../../utils/arnRegularExpressions');

module.exports = {
compileAuthorizers() {
Expand All @@ -23,20 +24,25 @@ module.exports = {

const authorizerLogicalId = this.provider.naming.getAuthorizerLogicalId(authorizer.name);

if (typeof authorizer.arn === 'string' && authorizer.arn.match(/^arn:aws:cognito-idp/)) {
if (typeof authorizer.arn === 'string'
&& authorizer.arn.match(awsArnRegExs.cognitoIdpArnExpr)) {
authorizerProperties.Type = 'COGNITO_USER_POOLS';
authorizerProperties.ProviderARNs = [authorizer.arn];
} else {
authorizerProperties.AuthorizerUri =
{ 'Fn::Join': ['',
[
'arn:aws:apigateway:',
{ Ref: 'AWS::Region' },
':lambda:path/2015-03-31/functions/',
authorizer.arn,
'/invocations',
{
'Fn::Join': ['',
[
'arn:',
{ Ref: 'AWS::Partition' },
':apigateway:',
{ Ref: 'AWS::Region' },
':lambda:path/2015-03-31/functions/',
authorizer.arn,
'/invocations',
],
],
] };
};
authorizerProperties.Type = authorizer.type ? authorizer.type.toUpperCase() : 'TOKEN';
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,18 @@ describe('#compileAuthorizers()', () => {

expect(resource.Type).to.equal('AWS::ApiGateway::Authorizer');
expect(resource.Properties.AuthorizerResultTtlInSeconds).to.equal(300);
expect(resource.Properties.AuthorizerUri).to.deep.equal({ 'Fn::Join': ['',
[
'arn:aws:apigateway:',
{ Ref: 'AWS::Region' },
':lambda:path/2015-03-31/functions/',
{ 'Fn::GetAtt': ['SomeLambdaFunction', 'Arn'] },
'/invocations',
expect(resource.Properties.AuthorizerUri).to.deep.equal({
'Fn::Join': ['',
[
'arn:',
{ Ref: 'AWS::Partition' },
':apigateway:',
{ Ref: 'AWS::Region' },
':lambda:path/2015-03-31/functions/',
{ 'Fn::GetAtt': ['SomeLambdaFunction', 'Arn'] },
'/invocations',
],
],
],
});
expect(resource.Properties.IdentitySource).to.equal('method.request.header.Authorization');
expect(resource.Properties.IdentityValidationExpression).to.equal(undefined);
Expand Down Expand Up @@ -77,15 +80,18 @@ describe('#compileAuthorizers()', () => {
.compiledCloudFormationTemplate.Resources.AuthorizerApiGatewayAuthorizer;

expect(resource.Type).to.equal('AWS::ApiGateway::Authorizer');
expect(resource.Properties.AuthorizerUri).to.deep.equal({ 'Fn::Join': ['',
[
'arn:aws:apigateway:',
{ Ref: 'AWS::Region' },
':lambda:path/2015-03-31/functions/',
'foo',
'/invocations',
expect(resource.Properties.AuthorizerUri).to.deep.equal({
'Fn::Join': ['',
[
'arn:',
{ Ref: 'AWS::Partition' },
':apigateway:',
{ Ref: 'AWS::Region' },
':lambda:path/2015-03-31/functions/',
'foo',
'/invocations',
],
],
],
});
expect(resource.Properties.AuthorizerResultTtlInSeconds).to.equal(500);
expect(resource.Properties.IdentitySource).to.equal('method.request.header.Custom');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,11 @@ module.exports = {
'https://',
this.provider.getApiGatewayRestApiId(),
`.execute-api.${
this.provider.getRegion()
}.amazonaws.com/${
this.provider.getStage()
this.provider.getRegion()
}.`,
{ Ref: 'AWS::URLSuffix' },
`/${
this.provider.getStage()
}`,
],
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,9 @@ describe('#compileDeployment()', () => {
[
'https://',
{ Ref: awsCompileApigEvents.apiGatewayRestApiLogicalId },
'.execute-api.us-east-1.amazonaws.com/dev',
'.execute-api.us-east-1.',
{ Ref: 'AWS::URLSuffix' },
'/dev',
],
],
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use strict';

const _ = require('lodash');
const awsArnRegExs = require('../../../../../../utils/arnRegularExpressions');

module.exports = {
getMethodAuthorization(http) {
Expand All @@ -18,7 +19,8 @@ module.exports = {

let authorizationType;
const authorizerArn = http.authorizer.arn;
if (typeof authorizerArn === 'string' && authorizerArn.match(/^arn:aws:cognito-idp/)) {
if (typeof authorizerArn === 'string'
&& authorizerArn.match(awsArnRegExs.cognitoIdpArnExpr)) {
authorizationType = 'COGNITO_USER_POOLS';
} else {
authorizationType = 'CUSTOM';
Expand Down
Loading