diff --git a/packages/amplify-category-storage/src/index.js b/packages/amplify-category-storage/src/index.js index 1a995e85943..24123c01eb6 100644 --- a/packages/amplify-category-storage/src/index.js +++ b/packages/amplify-category-storage/src/index.js @@ -71,14 +71,14 @@ async function getPermissionPolicies(context, resourceOpsMapping) { if (providerPlugin) { const providerController = require(`./provider-utils/${providerPlugin}`); - const { policy, attributes } = providerController.getPermissionPolicies( + const { policies, attributes } = providerController.getPermissionPolicies( context, service, resourceName, resourceOpsMapping[resourceName], ); - permissionPolicies.push(policy); + permissionPolicies.push(...policies); resourceAttributes.push({ resourceName, attributes, category }); } else { context.print.error(`Provider not configured for ${category}: ${resourceName}`); diff --git a/packages/amplify-category-storage/src/provider-utils/awscloudformation/service-walkthroughs/s3-walkthrough.js b/packages/amplify-category-storage/src/provider-utils/awscloudformation/service-walkthroughs/s3-walkthrough.js index d82526743ee..679ecbe2052 100644 --- a/packages/amplify-category-storage/src/provider-utils/awscloudformation/service-walkthroughs/s3-walkthrough.js +++ b/packages/amplify-category-storage/src/provider-utils/awscloudformation/service-walkthroughs/s3-walkthrough.js @@ -937,7 +937,7 @@ async function addTrigger(context, resourceName, triggerFunction, adminTriggerFu Statement: [ { Effect: 'Allow', - Action: ['s3:PutObject', 's3:GetObject', 's3:ListBucket', 's3:DeleteObject'], + Action: ['s3:PutObject', 's3:GetObject', 's3:DeleteObject'], Resource: [ { 'Fn::Join': [ @@ -953,6 +953,23 @@ async function addTrigger(context, resourceName, triggerFunction, adminTriggerFu }, ], }, + { + Effect: 'Allow', + Action: 's3:ListBucket', + Resource: [ + { + 'Fn::Join': [ + '', + [ + 'arn:aws:s3:::', + { + Ref: 'S3Bucket', + }, + ], + ], + }, + ], + }, ], }, }, @@ -1282,7 +1299,7 @@ function convertToCRUD(parameters, answers) { } export const getIAMPolicies = (resourceName, crudOptions) => { - let policy = {}; + let policies = []; let actions = new Set(); crudOptions.forEach(crudOption => { @@ -1306,7 +1323,28 @@ export const getIAMPolicies = (resourceName, crudOptions) => { }); actions = Array.from(actions); - policy = { + let listBucketPolicy = {}; + if (actions.includes('s3:ListBucket')) { + listBucketPolicy = { + Effect: 'Allow', + Action: 's3:ListBucket', + Resource: [ + { + 'Fn::Join': [ + '', + [ + 'arn:aws:s3:::', + { + Ref: `${category}${resourceName}BucketName`, + }, + ], + ], + }, + ], + }; + actions = actions.filter(action => action != 's3:ListBucket'); + } + let policy = { Effect: 'Allow', Action: actions, Resource: [ @@ -1324,10 +1362,11 @@ export const getIAMPolicies = (resourceName, crudOptions) => { }, ], }; - + // push both policies + policies.push(policy, listBucketPolicy); const attributes = ['BucketName']; - return { policy, attributes }; + return { policies, attributes }; }; function getTriggersForLambdaConfiguration(protectionLevel, functionName) { diff --git a/packages/amplify-e2e-tests/src/__tests__/function_2.test.ts b/packages/amplify-e2e-tests/src/__tests__/function_2.test.ts index 7f57172ff20..08a7f1f65d0 100644 --- a/packages/amplify-e2e-tests/src/__tests__/function_2.test.ts +++ b/packages/amplify-e2e-tests/src/__tests__/function_2.test.ts @@ -1,10 +1,14 @@ import { addApiWithSchema, + addAuthWithDefault, addDDBWithTrigger, addFunction, + addS3StorageWithSettings, addSimpleDDB, + AddStorageSettings, amplifyPush, amplifyPushAuth, + amplifyPushForce, createNewProjectDir, deleteProject, deleteProjectDir, @@ -33,6 +37,61 @@ describe('nodejs', () => { deleteProjectDir(projRoot); }); + it('lambda with s3 permissions should be able to call listObjects', async () => { + await initJSProjectWithProfile(projRoot, {}); + const random = Math.floor(Math.random() * 10000); + const fnName = `integtestfn${random}`; + const s3Name = `integtestfn${random}`; + const options: AddStorageSettings = { + resourceName: s3Name, + bucketName: s3Name, + }; + await addAuthWithDefault(projRoot); + await addS3StorageWithSettings(projRoot, options); + await addFunction( + projRoot, + { + name: fnName, + functionTemplate: 'Hello World', + additionalPermissions: { + permissions: ['storage'], + resources: [s3Name], + choices: ['auth', 'storage', 'function', 'api'], + operations: ['create', 'update', 'read', 'delete'], + }, + }, + 'nodejs', + ); + + overrideFunctionSrc( + projRoot, + fnName, + ` + const AWS = require('aws-sdk'); + const awsS3Client = new AWS.S3(); + + exports.handler = function(event, context) { + let listObjects = await awsS3Client + .listObjectsV2({ + Bucket: process.env.STORAGE_INTEGTESTFN${random}_BUCKETNAME, + }) + .promise(); + return listObjects + } + `, + ); + await amplifyPushForce(projRoot); + const meta = getProjectMeta(projRoot); + const { BucketName: bucketName, Region: region } = Object.keys(meta.storage).map(key => meta.storage[key])[0].output; + expect(bucketName).toBeDefined(); + expect(region).toBeDefined(); + const { Name: functionName } = Object.keys(meta.function).map(key => meta.function[key])[0].output; + expect(functionName).toBeDefined(); + const result1 = await invokeFunction(functionName, null, region); + expect(result1.StatusCode).toBe(200); + expect(result1.Payload).toBeDefined(); + }); + it('lambda with dynamoDB permissions should be able to scan ddb', async () => { await initJSProjectWithProfile(projRoot, {});