Skip to content

Commit

Permalink
Use correct appsync IAM permission policy for functions.
Browse files Browse the repository at this point in the history
  • Loading branch information
Ross Ragsdale committed Nov 19, 2020
1 parent c82d642 commit 8810503
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 52 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -644,50 +644,16 @@ export const migrate = async context => {
});
};

export const getIAMPolicies = (resourceName, crudOptions, context) => {
export const getIAMPolicies = (resourceName, operations, context) => {
let policy = {};
const actions = [];

crudOptions.forEach(crudOption => {
switch (crudOption) {
case 'create':
actions.push('appsync:Create*', 'appsync:StartSchemaCreation', 'appsync:GraphQL');
break;
case 'update':
actions.push('appsync:Update*');
break;
case 'read':
actions.push('appsync:Get*', 'appsync:List*');
break;
case 'delete':
actions.push('appsync:Delete*');
break;
default:
console.log(`${crudOption} not supported`);
}
});
const resources = [];

operations.forEach(operation => resources.push(buildPolicyResource(resourceName, operation)));

policy = {
Effect: 'Allow',
Action: actions,
Resource: [
{
'Fn::Join': [
'',
[
'arn:aws:appsync:',
{ Ref: 'AWS::Region' },
':',
{ Ref: 'AWS::AccountId' },
':apis/',
{
Ref: `${category}${resourceName}GraphQLAPIIdOutput`,
},
'/*',
],
],
},
],
Action: ['appsync:GraphQL'],
Resource: resources,
};

const attributes = ['GraphQLAPIIdOutput', 'GraphQLAPIEndpointOutput'];
Expand All @@ -698,6 +664,25 @@ export const getIAMPolicies = (resourceName, crudOptions, context) => {
return { policy, attributes };
};

const buildPolicyResource = (resourceName, operation) => {
return {
'Fn::Join': [
'',
[
'arn:aws:appsync:',
{ Ref: 'AWS::Region' },
':',
{ Ref: 'AWS::AccountId' },
':apis/',
{
Ref: `${category}${resourceName}GraphQLAPIIdOutput`,
},
`/types/${operation}/*`,
],
],
};
};

const templateSchemaFilter = authConfig => {
const authIncludesCognito = getAuthTypes(authConfig).includes('AMAZON_COGNITO_USER_POOLS');
return (templateOption: ListChoiceOptions): boolean =>
Expand Down
6 changes: 6 additions & 0 deletions packages/amplify-category-function/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,9 @@ export enum CRUDOperation {
UPDATE = 'update',
DELETE = 'delete',
}

export enum GraphQLOperation {
QUERY = 'Query',
MUTATION = 'Mutation',
SUBSCRIPTION = 'Subscription',
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { constructCFModelTableNameComponent, constructCFModelTableArnComponent }
import inquirer from 'inquirer';
import path from 'path';
import * as TransformPackage from 'graphql-transformer-core';
import _ from 'lodash';
import { topLevelCommentPrefix, topLevelCommentSuffix, envVarPrintoutPrefix, CRUDOperation } from '../../../constants';
import _, { get } from 'lodash';
import { topLevelCommentPrefix, topLevelCommentSuffix, envVarPrintoutPrefix, CRUDOperation, GraphQLOperation } from '../../../constants';
import { ServiceName } from '../utils/constants';
import {
fetchPermissionCategories,
Expand Down Expand Up @@ -49,6 +49,7 @@ export const askExecRolePermissionsQuestions = async (
let categoryPolicies = [];
let resources = [];
const crudOptions = _.values(CRUDOperation);
const graphqlOperations = _.values(GraphQLOperation);
const permissions = {};

const backendDir = context.amplify.pathManager.getBackendDirPath();
Expand Down Expand Up @@ -107,6 +108,19 @@ export const askExecRolePermissionsQuestions = async (
}

for (let resourceName of selectedResources) {
// If the resource is AppSync, use GraphQL operations for permission policies.
// Otherwise, default to CRUD permissions.
const serviceType = get(context.amplify.getProjectMeta(), [category, resourceName, 'service']);
let options;
switch (serviceType) {
case 'AppSync':
options = graphqlOperations;
break;
default:
options = crudOptions;
break;
}

if (
// In case of some resources they are not in the meta file so check for resource existence as well
amplifyMeta[category] &&
Expand All @@ -116,25 +130,24 @@ export const askExecRolePermissionsQuestions = async (
context.print.warning(`Policies cannot be added for ${category}/${resourceName}, since it is a MobileHub imported resource.`);
continue;
} else {
const crudPermissionQuestion = {
const permissionQuestion = {
type: 'checkbox',
name: 'crudOptions',
name: 'options',
message: `Select the operations you want to permit for ${resourceName}`,
choices: crudOptions,
choices: options,
validate: value => {
if (value.length === 0) {
return 'You must select at least one operation';
}

return true;
},
default: fetchPermissionsForResourceInCategory(currentPermissionMap, category, resourceName),
default: fetchPermissionsForResourceInCategory(currentPermissionMap, category, resourceName, serviceType),
};

const crudPermissionAnswer = await inquirer.prompt([crudPermissionQuestion]);
const permissionAnswer = await inquirer.prompt([permissionQuestion]);

const resourcePolicy: any = crudPermissionAnswer.crudOptions;
// overload crudOptions when user selects graphql @model-backing DynamoDB table
const resourcePolicy: any = permissionAnswer.options;
// overload options when user selects graphql @model-backing DynamoDB table
// as there is no actual storage category resource where getPermissionPolicies can derive service and provider
if (resourceName.endsWith(appsyncTableSuffix)) {
resourcePolicy.providerPlugin = 'awscloudformation';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,34 @@ export const fetchPermissionResourcesForCategory = (permissionMap, category: str
return _.keys(_.get(permissionMap, [category]));
};

export const fetchPermissionsForResourceInCategory = (permissionMap, category: string, resourceName: string) => {
return _.get(permissionMap, [category, resourceName]);
export const fetchPermissionsForResourceInCategory = (permissionMap, category: string, resourceName: string, serviceType: string) => {
const permissions = _.get(permissionMap, [category, resourceName]);
// BACKWARDS COMPATIBILITY:
// https://github.com/aws-amplify/amplify-cli/pull/5342
// If AppSync and CRUD permissions, update to GraphQL
if (serviceType == 'AppSync' && hasCRUDPermissions(permissions)) {
console.log('Backwards compatibility');
return updatePermissionsForBackwardsCompatibility(permissionMap, category, resourceName);
} else {
return permissions;
}
};

const hasCRUDPermissions = permissions => {
return permissions.includes('create') || permissions.includes('read') || permissions.includes('update') || permissions.includes('delete');
};

const updatePermissionsForBackwardsCompatibility = (permissionMap, category, resourceName) => {
const permissions = _.get(permissionMap, [category, resourceName]);
const newPermissions = [];
// create || update || delete => mutation
// read => query, subscription
if (permissions.includes('create') || permissions.includes('update') || permissions.includes('delete')) {
newPermissions.push('Mutation');
}
if (_.includes(permissions, 'read')) {
newPermissions.push('Query');
newPermissions.push('Subscription');
}
return newPermissions;
};

0 comments on commit 8810503

Please sign in to comment.