Skip to content

Commit

Permalink
fix(AWS API Gateway): Fix schema for apiKeys and permissionsBoundary
Browse files Browse the repository at this point in the history
  • Loading branch information
lyndoh committed May 18, 2021
1 parent ed25868 commit 5601025
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 106 deletions.
31 changes: 19 additions & 12 deletions lib/plugins/aws/provider.js
Original file line number Diff line number Diff line change
Expand Up @@ -276,27 +276,34 @@ class AwsProvider {
anyOf: [
{ type: 'string' },
{
type: 'object',
properties: {
name: { type: 'string' },
value: { type: 'string' },
description: { type: 'string' },
customerId: { type: 'string' },
},
anyOf: [{ required: ['name'] }, { required: ['value'] }],
additionalProperties: false,
$ref: '#/definitions/awsApiGatewayApiKeysProperties',
},
{
type: 'object',
maxProperties: 1,
additionalProperties: {
type: 'array',
items: { type: 'string' },
items: {
oneOf: [
{ type: 'string' },
{ $ref: '#/definitions/awsApiGatewayApiKeysProperties' },
],
},
},
},
],
},
},
awsApiGatewayApiKeysProperties: {
type: 'object',
properties: {
name: { type: 'string' },
value: { type: 'string' },
description: { type: 'string' },
customerId: { type: 'string' },
},
additionalProperties: false,
},
awsArn: {
anyOf: [
{ $ref: '#/definitions/awsArnString' },
Expand Down Expand Up @@ -907,8 +914,8 @@ class AwsProvider {
items: { $ref: '#/definitions/awsArn' },
},
statements: { $ref: '#/definitions/awsIamPolicyStatements' },
permissionBoundary: { $ref: '#/definitions/awsArnString' },
permissionsBoundary: { $ref: '#/definitions/awsArnString' },
permissionBoundary: { $ref: '#/definitions/awsArn' },
permissionsBoundary: { $ref: '#/definitions/awsArn' },
tags: { $ref: '#/definitions/awsResourceTags' },
},
additionalProperties: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const expect = require('chai').expect;
const AwsCompileApigEvents = require('../../../../../../../../../../lib/plugins/aws/package/compile/events/apiGateway/index');
const Serverless = require('../../../../../../../../../../lib/Serverless');
const AwsProvider = require('../../../../../../../../../../lib/plugins/aws/provider');
const runServerless = require('../../../../../../../../../utils/run-serverless');

describe('#compileApiKeys()', () => {
let serverless;
Expand Down Expand Up @@ -135,101 +136,79 @@ describe('#compileApiKeys()', () => {
).to.equal('ApiGatewayDeploymentTest');
});
});
});

describe('lib/plugins/aws/package/compile/events/apiGateway/lib/apiKeys.test.js', () => {
it('should support usage plan notation', async () => {
const apiGatewayExt = {
apiKeys: [
{
free: [
'1234567890',
{ name: '2345678901' },
{
value: 'valueForKeyWithoutName',
description: 'Api key description',
},
{ name: '3456789012', value: 'valueForKey3456789012' },
{ description: 'descriptionForKeyWithoutNameOrValue' },
],
},
{ paid: ['0987654321', 'jihgfedcba'] },
],
usagePlan: [{ free: {} }, { paid: {} }],
};
const { awsNaming, cfTemplate, serverless } = await runServerless({
fixture: 'apiGateway',
command: 'package',
configExt: {
provider: {
apiGateway: apiGatewayExt,
},
},
});
const resources = cfTemplate.Resources;

describe('when using usage plan notation', () => {
it('should support usage plan notation', () => {
awsCompileApigEvents.serverless.service.provider.apiGateway = {
apiKeys: [
{
free: [
'1234567890',
{ name: '2345678901' },
{
value: 'valueForKeyWithoutName',
description: 'Api key description',
},
{ name: '3456789012', value: 'valueForKey3456789012' },
],
},
{ paid: ['0987654321', 'jihgfedcba'] },
],
usagePlan: [{ free: [] }, { paid: [] }],
};

awsCompileApigEvents.compileApiKeys();
const expectedApiKeys = {
free: [
{ name: '1234567890', value: undefined, description: undefined },
{ name: '2345678901', value: undefined, description: undefined },
{
name: undefined,
value: 'valueForKeyWithoutName',
description: 'Api key description',
},
{
name: '3456789012',
value: 'valueForKey3456789012',
description: undefined,
},
],
paid: [
{ name: '0987654321', value: undefined, description: undefined },
{ name: 'jihgfedcba', value: undefined, description: undefined },
],
};
awsCompileApigEvents.serverless.service.provider.apiGateway.apiKeys.forEach((plan) => {
const planName = Object.keys(plan)[0]; // free || paid
const apiKeys = expectedApiKeys[planName];
apiKeys.forEach((apiKey, index) => {
expect(
awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
.Resources[
awsCompileApigEvents.provider.naming.getApiKeyLogicalId(index + 1, planName)
].Type
).to.equal('AWS::ApiGateway::ApiKey');
expect(
awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
.Resources[
awsCompileApigEvents.provider.naming.getApiKeyLogicalId(index + 1, planName)
].Properties.Enabled
).to.equal(true);
expect(
awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
.Resources[
awsCompileApigEvents.provider.naming.getApiKeyLogicalId(index + 1, planName)
].Properties.Name
).to.equal(apiKey.name);
expect(
awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
.Resources[
awsCompileApigEvents.provider.naming.getApiKeyLogicalId(index + 1, planName)
].Properties.Description
).to.equal(apiKey.description);
expect(
awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
.Resources[
awsCompileApigEvents.provider.naming.getApiKeyLogicalId(index + 1, planName)
].Properties.Value
).to.equal(apiKey.value);
expect(
awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
.Resources[
awsCompileApigEvents.provider.naming.getApiKeyLogicalId(index + 1, planName)
].Properties.StageKeys[0].RestApiId.Ref
).to.equal('ApiGatewayRestApi');
expect(
awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
.Resources[
awsCompileApigEvents.provider.naming.getApiKeyLogicalId(index + 1, planName)
].Properties.StageKeys[0].StageName
).to.equal('dev');
expect(
awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
.Resources[
awsCompileApigEvents.provider.naming.getApiKeyLogicalId(index + 1, planName)
].DependsOn
).to.equal('ApiGatewayDeploymentTest');
});
const expectedApiKeys = {
free: [
{ name: '1234567890', value: undefined, description: undefined },
{ name: '2345678901', value: undefined, description: undefined },
{
name: undefined,
value: 'valueForKeyWithoutName',
description: 'Api key description',
},
{
name: '3456789012',
value: 'valueForKey3456789012',
description: undefined,
},
{
name: undefined,
value: undefined,
description: 'descriptionForKeyWithoutNameOrValue',
},
],
paid: [
{ name: '0987654321', value: undefined, description: undefined },
{ name: 'jihgfedcba', value: undefined, description: undefined },
],
};
apiGatewayExt.apiKeys.forEach((plan) => {
const planName = Object.keys(plan)[0]; // free || paid
const apiKeys = expectedApiKeys[planName];
apiKeys.forEach((apiKey, index) => {
const resource = resources[awsNaming.getApiKeyLogicalId(index + 1, planName)];
expect(resource.Type).to.equal('AWS::ApiGateway::ApiKey');
expect(resource.Properties.Enabled).to.equal(true);
expect(resource.Properties.Name).to.equal(apiKey.name);
expect(resource.Properties.Description).to.equal(apiKey.description);
expect(resource.Properties.Value).to.equal(apiKey.value);
expect(resource.Properties.StageKeys[0].RestApiId.Ref).to.equal('ApiGatewayRestApi');
expect(resource.Properties.StageKeys[0].StageName).to.equal('dev');
expect(resource.DependsOn).to.equal(
awsNaming.generateApiGatewayDeploymentLogicalId(serverless.instanceId)
);
});
});
});
Expand Down
26 changes: 26 additions & 0 deletions test/unit/lib/plugins/aws/package/lib/mergeIamTemplates.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,32 @@ describe('lib/plugins/aws/package/lib/mergeIamTemplates.test.js', () => {
);
});

it('should support `provider.iam.role.permissionsBoundary` defined with CF intrinsic functions', async () => {
const { cfTemplate, awsNaming } = await runServerless({
fixture: 'function',
command: 'package',
configExt: {
provider: {
iam: {
role: {
permissionsBoundary: {
'Fn::Sub': 'arn:aws:iam::${AWS::AccountId}:policy/XCompanyBoundaries',
},
},
},
},
},
});

const IamRoleLambdaExecution = awsNaming.getRoleLogicalId();
const {
Properties: { PermissionsBoundary },
} = cfTemplate.Resources[IamRoleLambdaExecution];
expect(PermissionsBoundary).to.deep.includes({
'Fn::Sub': 'arn:aws:iam::${AWS::AccountId}:policy/XCompanyBoundaries',
});
});

it('should ensure needed IAM configuration when `provider.vpc` is configured', () => {
const IamRoleLambdaExecution = naming.getRoleLogicalId();
const iamResource = cfResources[IamRoleLambdaExecution];
Expand Down

0 comments on commit 5601025

Please sign in to comment.