Skip to content

Commit fee247c

Browse files
feat: flow to add policies to access amplify resources from Lambda (#1462)
* feat: flow to add policies to access amplify resources from Lambda * changes based on feedback * fix variable names * chnage var names to camel-case * fix error messages * fix region string issue * fix wording for resource selection * fix resource cyclic-dependency issues * change JSON.parse to context.amplify.readJSON * modify AppSync CRUD permissions
1 parent 18508d9 commit fee247c

File tree

31 files changed

+1330
-21
lines changed

31 files changed

+1330
-21
lines changed

packages/amplify-category-analytics/index.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,43 @@ const {
33
migrate,
44
} = require('./provider-utils/awscloudformation/service-walkthroughs/pinpoint-walkthrough');
55

6+
const category = 'analytics';
7+
68
function console(context) {
79
pinpointHelper.console(context);
810
}
911

12+
async function getPermissionPolicies(context, resourceOpsMapping) {
13+
const amplifyMetaFilePath = context.amplify.pathManager.getAmplifyMetaFilePath();
14+
const amplifyMeta = context.amplify.readJsonFile(amplifyMetaFilePath);
15+
const permissionPolicies = [];
16+
const resourceAttributes = [];
17+
18+
Object.keys(resourceOpsMapping).forEach((resourceName) => {
19+
try {
20+
const providerController = require(`./provider-utils/${amplifyMeta[category][resourceName].providerPlugin}/index`);
21+
if (providerController) {
22+
const { policy, attributes } = providerController.getPermissionPolicies(
23+
context,
24+
amplifyMeta[category][resourceName].service,
25+
resourceName,
26+
resourceOpsMapping[resourceName],
27+
);
28+
permissionPolicies.push(policy);
29+
resourceAttributes.push({ resourceName, attributes, category });
30+
} else {
31+
context.print.error(`Provider not configured for ${category}: ${resourceName}`);
32+
}
33+
} catch (e) {
34+
context.print.warning(`Could not get policies for ${category}: ${resourceName}`);
35+
throw e;
36+
}
37+
});
38+
return { permissionPolicies, resourceAttributes };
39+
}
40+
1041
module.exports = {
1142
console,
1243
migrate,
44+
getPermissionPolicies,
1345
};

packages/amplify-category-analytics/provider-utils/awscloudformation/index.js

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
function addResource(context, category, service) {
32
const serviceMetadata = context.amplify.readJsonFile(`${__dirname}/../supported-services.json`)[service];
43
const { defaultValuesFilename, serviceWalkthroughFilename } = serviceMetadata;
@@ -9,4 +8,18 @@ function addResource(context, category, service) {
98
return addWalkthrough(context, defaultValuesFilename, serviceMetadata);
109
}
1110

12-
module.exports = { addResource };
11+
function getPermissionPolicies(context, service, resourceName, crudOptions) {
12+
const serviceMetadata = context.amplify.readJsonFile(`${__dirname}/../supported-services.json`)[service];
13+
const { serviceWalkthroughFilename } = serviceMetadata;
14+
const serviceWalkthroughSrc = `${__dirname}/service-walkthroughs/${serviceWalkthroughFilename}`;
15+
const { getIAMPolicies } = require(serviceWalkthroughSrc);
16+
17+
if (!getPermissionPolicies) {
18+
context.print.info(`No policies found for ${resourceName}`);
19+
return;
20+
}
21+
22+
return getIAMPolicies(resourceName, crudOptions);
23+
}
24+
25+
module.exports = { addResource, getPermissionPolicies };

packages/amplify-category-analytics/provider-utils/awscloudformation/service-walkthroughs/pinpoint-walkthrough.js

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -343,4 +343,56 @@ function isRefNode(node, refName) {
343343
return false;
344344
}
345345

346-
module.exports = { addWalkthrough, migrate };
346+
function getIAMPolicies(resourceName, crudOptions) {
347+
let policy = {};
348+
const actions = [];
349+
350+
crudOptions.forEach((crudOption) => {
351+
switch (crudOption) {
352+
case 'create': actions.push(
353+
'mobiletargeting:Put*',
354+
'mobiletargeting:Create*',
355+
'mobiletargeting:Send*',
356+
);
357+
break;
358+
case 'update': actions.push('mobiletargeting:Update*');
359+
break;
360+
case 'read': actions.push('mobiletargeting:Get*', 'mobiletargeting:List*');
361+
break;
362+
case 'delete': actions.push('mobiletargeting:Delete*');
363+
break;
364+
default: console.log(`${crudOption} not supported`);
365+
}
366+
});
367+
368+
policy = {
369+
Effect: 'Allow',
370+
Action: actions,
371+
Resource: [
372+
{
373+
'Fn::Join': [
374+
'',
375+
[
376+
'arn:aws:mobiletargeting:',
377+
{
378+
Ref: `${category}${resourceName}Region`,
379+
},
380+
':',
381+
{ Ref: 'AWS::AccountId' },
382+
':apps/',
383+
{
384+
Ref: `${category}${resourceName}Id`,
385+
},
386+
],
387+
],
388+
},
389+
],
390+
};
391+
392+
const attributes = ['Id', 'Region'];
393+
394+
return { policy, attributes };
395+
}
396+
397+
398+
module.exports = { addWalkthrough, migrate, getIAMPolicies };

packages/amplify-category-api/index.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,8 +133,38 @@ async function initEnv(context) {
133133
});
134134
}
135135

136+
async function getPermissionPolicies(context, resourceOpsMapping) {
137+
const amplifyMetaFilePath = context.amplify.pathManager.getAmplifyMetaFilePath();
138+
const amplifyMeta = context.amplify.readJsonFile(amplifyMetaFilePath);
139+
const permissionPolicies = [];
140+
const resourceAttributes = [];
141+
142+
Object.keys(resourceOpsMapping).forEach((resourceName) => {
143+
try {
144+
const providerController = require(`./provider-utils/${amplifyMeta[category][resourceName].providerPlugin}/index`);
145+
if (providerController) {
146+
const { policy, attributes } = providerController.getPermissionPolicies(
147+
context,
148+
amplifyMeta[category][resourceName].service,
149+
resourceName,
150+
resourceOpsMapping[resourceName],
151+
);
152+
permissionPolicies.push(policy);
153+
resourceAttributes.push({ resourceName, attributes, category });
154+
} else {
155+
context.print.error(`Provider not configured for ${category}: ${resourceName}`);
156+
}
157+
} catch (e) {
158+
context.print.warning(`Could not get policies for ${category}: ${resourceName}`);
159+
throw e;
160+
}
161+
});
162+
return { permissionPolicies, resourceAttributes };
163+
}
164+
136165
module.exports = {
137166
console,
138167
migrate,
139168
initEnv,
169+
getPermissionPolicies,
140170
};

packages/amplify-category-api/provider-utils/awscloudformation/index.js

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,20 @@ function addDatasource(context, category, datasource) {
170170
return serviceQuestions(context, defaultValuesFilename, serviceWalkthroughFilename);
171171
}
172172

173+
function getPermissionPolicies(context, service, resourceName, crudOptions) {
174+
serviceMetadata = context.amplify.readJsonFile(`${__dirname}/../supported-services.json`)[service];
175+
const { serviceWalkthroughFilename } = serviceMetadata;
176+
const serviceWalkthroughSrc = `${__dirname}/service-walkthroughs/${serviceWalkthroughFilename}`;
177+
const { getIAMPolicies } = require(serviceWalkthroughSrc);
178+
179+
if (!getPermissionPolicies) {
180+
context.print.info(`No policies found for ${resourceName}`);
181+
return;
182+
}
183+
184+
return getIAMPolicies(resourceName, crudOptions);
185+
}
186+
173187
module.exports = {
174-
addResource, updateResource, console, migrateResource, addDatasource,
188+
addResource, updateResource, console, migrateResource, addDatasource, getPermissionPolicies,
175189
};

packages/amplify-category-api/provider-utils/awscloudformation/service-walkthroughs/apigw-walkthrough.js

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -637,4 +637,59 @@ function convertToCRUD(privacy) {
637637
return privacy;
638638
}
639639

640-
module.exports = { serviceWalkthrough, updateWalkthrough, migrate };
640+
641+
function getIAMPolicies(resourceName, crudOptions) {
642+
let policy = {};
643+
const actions = [];
644+
645+
crudOptions.forEach((crudOption) => {
646+
switch (crudOption) {
647+
case 'create': actions.push(
648+
'apigateway:POST',
649+
'apigateway:PUT',
650+
);
651+
break;
652+
case 'update': actions.push('apigateway:PATCH');
653+
break;
654+
case 'read': actions.push(
655+
'apigateway:GET', 'apigateway:HEAD',
656+
'apigateway:OPTIONS',
657+
);
658+
break;
659+
case 'delete': actions.push('apigateway:DELETE');
660+
break;
661+
default: console.log(`${crudOption} not supported`);
662+
}
663+
});
664+
665+
policy = {
666+
Effect: 'Allow',
667+
Action: actions,
668+
Resource: [
669+
{
670+
'Fn::Join': [
671+
'',
672+
[
673+
'arn:aws:apigateway:',
674+
{
675+
Ref: 'AWS::Region',
676+
},
677+
'::/restapis/',
678+
{
679+
Ref: `${category}${resourceName}ApiName`,
680+
},
681+
'/*',
682+
],
683+
],
684+
},
685+
],
686+
};
687+
688+
const attributes = ['ApiName'];
689+
690+
return { policy, attributes };
691+
}
692+
693+
module.exports = {
694+
serviceWalkthrough, updateWalkthrough, migrate, getIAMPolicies,
695+
};

packages/amplify-category-api/provider-utils/awscloudformation/service-walkthroughs/appSync-walkthrough.js

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,52 @@ async function migrate(context) {
375375
await context.amplify.executeProviderUtils(context, 'awscloudformation', 'compileSchema', { noConfig: true, forceCompile: true, migrate: true });
376376
}
377377

378+
function getIAMPolicies(resourceName, crudOptions) {
379+
let policy = {};
380+
const actions = [];
381+
382+
crudOptions.forEach((crudOption) => {
383+
switch (crudOption) {
384+
case 'create': actions.push('appsync:Create*', 'appsync:StartSchemaCreation', 'appsync:GraphQL');
385+
break;
386+
case 'update': actions.push('appsync:Update*');
387+
break;
388+
case 'read': actions.push('appsync:Get*', 'appsync:List*');
389+
break;
390+
case 'delete': actions.push('appsync:Delete*');
391+
break;
392+
default: console.log(`${crudOption} not supported`);
393+
}
394+
});
395+
396+
policy = {
397+
Effect: 'Allow',
398+
Action: actions,
399+
Resource: [
400+
{
401+
'Fn::Join': [
402+
'',
403+
[
404+
'arn:aws:appsync:',
405+
{ Ref: 'AWS::Region' },
406+
':',
407+
{ Ref: 'AWS::AccountId' },
408+
':apis/',
409+
{
410+
Ref: `${category}${resourceName}GraphQLAPIIdOutput`,
411+
},
412+
],
413+
],
414+
},
415+
],
416+
};
417+
418+
const attributes = ['GraphQLAPIIdOutput'];
419+
420+
return { policy, attributes };
421+
}
422+
378423

379424
module.exports = {
380-
serviceWalkthrough, updateWalkthrough, openConsole, migrate,
425+
serviceWalkthrough, updateWalkthrough, openConsole, migrate, getIAMPolicies,
381426
};

packages/amplify-category-auth/index.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,11 +231,42 @@ async function console(context) {
231231
});
232232
}
233233

234+
async function getPermissionPolicies(context, resourceOpsMapping) {
235+
const amplifyMetaFilePath = context.amplify.pathManager.getAmplifyMetaFilePath();
236+
const amplifyMeta = context.amplify.readJsonFile(amplifyMetaFilePath);
237+
const permissionPolicies = [];
238+
const resourceAttributes = [];
239+
240+
Object.keys(resourceOpsMapping).forEach((resourceName) => {
241+
try {
242+
const providerController = require(`./provider-utils/${amplifyMeta[category][resourceName].providerPlugin}/index`);
243+
if (providerController) {
244+
const { policy, attributes } = providerController.getPermissionPolicies(
245+
context,
246+
amplifyMeta[category][resourceName].service,
247+
resourceName,
248+
resourceOpsMapping[resourceName],
249+
);
250+
permissionPolicies.push(policy);
251+
resourceAttributes.push({ resourceName, attributes, category });
252+
} else {
253+
context.print.error(`Provider not configured for ${category}: ${resourceName}`);
254+
}
255+
} catch (e) {
256+
context.print.warning(`Could not get policies for ${category}: ${resourceName}`);
257+
throw e;
258+
}
259+
});
260+
return { permissionPolicies, resourceAttributes };
261+
}
262+
263+
234264
module.exports = {
235265
externalAuthEnable,
236266
checkRequirements,
237267
add,
238268
migrate,
239269
initEnv,
240270
console,
271+
getPermissionPolicies,
241272
};

packages/amplify-category-auth/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"chalk-pipe": "^1.2.0",
1818
"eslint": "^4.19.1",
1919
"inquirer": "^6.0.0",
20+
"fs-extra": "^7.0.0",
2021
"jest": "^23.5.0",
2122
"lodash": "^4.17.10",
2223
"opn": "^5.3.0",

packages/amplify-category-auth/provider-utils/awscloudformation/index.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,21 @@ async function openIdentityPoolConsole(context, region, identityPoolId) {
517517
context.print.success(identityPoolConsoleUrl);
518518
}
519519

520+
function getPermissionPolicies(context, service, resourceName, crudOptions) {
521+
serviceMetadata = context.amplify.readJsonFile(`${__dirname}/../supported-services.json`)[service];
522+
const { serviceWalkthroughFilename } = serviceMetadata;
523+
const serviceWalkthroughSrc = `${__dirname}/service-walkthroughs/${serviceWalkthroughFilename}`;
524+
const { getIAMPolicies } = require(serviceWalkthroughSrc);
525+
526+
if (!getPermissionPolicies) {
527+
context.print.info(`No policies found for ${resourceName}`);
528+
return;
529+
}
530+
531+
return getIAMPolicies(resourceName, crudOptions);
532+
}
533+
534+
520535
module.exports = {
521536
addResource,
522537
updateResource,
@@ -526,4 +541,5 @@ module.exports = {
526541
copyCfnTemplate,
527542
migrate,
528543
console,
544+
getPermissionPolicies,
529545
};

0 commit comments

Comments
 (0)