diff --git a/packages/amplify-category-api/package.json b/packages/amplify-category-api/package.json index f1aab9b1d0..cb12f6afa0 100644 --- a/packages/amplify-category-api/package.json +++ b/packages/amplify-category-api/package.json @@ -103,7 +103,7 @@ "coverageProvider": "v8", "coverageThreshold": { "global": { - "branches": 67, + "branches": 68, "functions": 42, "lines": 40 } diff --git a/packages/amplify-category-api/src/__tests__/graphql-transformer/override/__snapshots__/model-transformer-override.test.ts.snap b/packages/amplify-category-api/src/__tests__/graphql-transformer/override/__snapshots__/model-transformer-override.test.ts.snap index 5bf5f98768..853fb0e4a9 100644 --- a/packages/amplify-category-api/src/__tests__/graphql-transformer/override/__snapshots__/model-transformer-override.test.ts.snap +++ b/packages/amplify-category-api/src/__tests__/graphql-transformer/override/__snapshots__/model-transformer-override.test.ts.snap @@ -755,50 +755,6 @@ $util.toJson({})", }, "Type": "AWS::IAM::Role", }, - "PostIAMRoleDefaultPolicy04190CA0": Object { - "Properties": Object { - "PolicyDocument": Object { - "Statement": Array [ - Object { - "Action": Array [ - "dynamodb:BatchGetItem", - "dynamodb:GetRecords", - "dynamodb:GetShardIterator", - "dynamodb:Query", - "dynamodb:GetItem", - "dynamodb:Scan", - "dynamodb:ConditionCheckItem", - "dynamodb:BatchWriteItem", - "dynamodb:PutItem", - "dynamodb:UpdateItem", - "dynamodb:DeleteItem", - "dynamodb:DescribeTable", - ], - "Effect": "Allow", - "Resource": Array [ - Object { - "Fn::GetAtt": Array [ - "PostTable", - "Arn", - ], - }, - Object { - "Ref": "AWS::NoValue", - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "PostIAMRoleDefaultPolicy04190CA0", - "Roles": Array [ - Object { - "Ref": "PostIAMRole83BF708F", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, "PostTable": Object { "DeletionPolicy": "Delete", "Properties": Object { @@ -1569,50 +1525,6 @@ Object { }, "Type": "AWS::IAM::Role", }, - "CommentIAMRoleDefaultPolicyA8D6F6B5": Object { - "Properties": Object { - "PolicyDocument": Object { - "Statement": Array [ - Object { - "Action": Array [ - "dynamodb:BatchGetItem", - "dynamodb:GetRecords", - "dynamodb:GetShardIterator", - "dynamodb:Query", - "dynamodb:GetItem", - "dynamodb:Scan", - "dynamodb:ConditionCheckItem", - "dynamodb:BatchWriteItem", - "dynamodb:PutItem", - "dynamodb:UpdateItem", - "dynamodb:DeleteItem", - "dynamodb:DescribeTable", - ], - "Effect": "Allow", - "Resource": Array [ - Object { - "Fn::GetAtt": Array [ - "CommentTable", - "Arn", - ], - }, - Object { - "Ref": "AWS::NoValue", - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "CommentIAMRoleDefaultPolicyA8D6F6B5", - "Roles": Array [ - Object { - "Ref": "CommentIAMRoleD5EC5F51", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, "CommentTable": Object { "DeletionPolicy": "Delete", "Properties": Object { @@ -3094,50 +3006,6 @@ $util.toJson({})", }, "Type": "AWS::IAM::Role", }, - "PostIAMRoleDefaultPolicy04190CA0": Object { - "Properties": Object { - "PolicyDocument": Object { - "Statement": Array [ - Object { - "Action": Array [ - "dynamodb:BatchGetItem", - "dynamodb:GetRecords", - "dynamodb:GetShardIterator", - "dynamodb:Query", - "dynamodb:GetItem", - "dynamodb:Scan", - "dynamodb:ConditionCheckItem", - "dynamodb:BatchWriteItem", - "dynamodb:PutItem", - "dynamodb:UpdateItem", - "dynamodb:DeleteItem", - "dynamodb:DescribeTable", - ], - "Effect": "Allow", - "Resource": Array [ - Object { - "Fn::GetAtt": Array [ - "PostTable", - "Arn", - ], - }, - Object { - "Ref": "AWS::NoValue", - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "PostIAMRoleDefaultPolicy04190CA0", - "Roles": Array [ - Object { - "Ref": "PostIAMRole83BF708F", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, "PostTable": Object { "DeletionPolicy": "Delete", "Properties": Object { @@ -3880,50 +3748,6 @@ Object { }, "Type": "AWS::IAM::Role", }, - "CommentIAMRoleDefaultPolicyA8D6F6B5": Object { - "Properties": Object { - "PolicyDocument": Object { - "Statement": Array [ - Object { - "Action": Array [ - "dynamodb:BatchGetItem", - "dynamodb:GetRecords", - "dynamodb:GetShardIterator", - "dynamodb:Query", - "dynamodb:GetItem", - "dynamodb:Scan", - "dynamodb:ConditionCheckItem", - "dynamodb:BatchWriteItem", - "dynamodb:PutItem", - "dynamodb:UpdateItem", - "dynamodb:DeleteItem", - "dynamodb:DescribeTable", - ], - "Effect": "Allow", - "Resource": Array [ - Object { - "Fn::GetAtt": Array [ - "CommentTable", - "Arn", - ], - }, - Object { - "Ref": "AWS::NoValue", - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "CommentIAMRoleDefaultPolicyA8D6F6B5", - "Roles": Array [ - Object { - "Ref": "CommentIAMRoleD5EC5F51", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, "CommentTable": Object { "DeletionPolicy": "Delete", "Properties": Object { diff --git a/packages/amplify-e2e-core/src/utils/sdk-calls.ts b/packages/amplify-e2e-core/src/utils/sdk-calls.ts index 3444ca430d..7d4c2e8e1f 100644 --- a/packages/amplify-e2e-core/src/utils/sdk-calls.ts +++ b/packages/amplify-e2e-core/src/utils/sdk-calls.ts @@ -201,35 +201,7 @@ export const getAmplifyBackendJobStatus = async (jobId: string, appId: string, e .promise(); }; -export const listRoleNamesContaining = async (searchString: string, region: string): Promise => { - const service = new IAM({ region }); - - const roles: string[] = []; - let isTruncated = true; - let marker: string | undefined; - - while (isTruncated) { - const params = marker ? { Marker: marker } : {}; - const response = await service.listRoles(params).promise(); - - const matchingRoles = response.Roles.filter((role) => role.RoleName.includes(searchString)); - roles.push(...matchingRoles.map((r) => r.RoleName)); - - isTruncated = response.IsTruncated; - marker = response.Marker; - } - - return roles; -}; - -export const getRolePolicy = async (roleName: string, policyName: string, region: string): Promise => { - const service = new IAM({ region }); - const rawDocument = (await service.getRolePolicy({ PolicyName: policyName, RoleName: roleName }).promise()).PolicyDocument; - const decodedDocument = decodeURIComponent(rawDocument); - return JSON.parse(decodedDocument); -}; - -export const listRolePolicies = async (roleName: string, region: string): Promise => { +export const listRolePolicies = async (roleName: string, region: string) => { const service = new IAM({ region }); return (await service.listRolePolicies({ RoleName: roleName }).promise()).PolicyNames; }; diff --git a/packages/amplify-e2e-tests/src/__tests__/api-override-ddb-table-name.test.ts b/packages/amplify-e2e-tests/src/__tests__/api-override-ddb-table-name.test.ts deleted file mode 100644 index 8884f9cfa5..0000000000 --- a/packages/amplify-e2e-tests/src/__tests__/api-override-ddb-table-name.test.ts +++ /dev/null @@ -1,119 +0,0 @@ -import path from 'path'; -import * as fs from 'fs-extra'; -import { - addApiWithoutSchema, - amplifyOverrideApi, - amplifyPush, - amplifyPushOverride, - createNewProjectDir, - deleteProject, - deleteProjectDir, - getAppSyncApi, - getDDBTable, - getProjectMeta, - getRolePolicy, - initJSProjectWithProfile, - listRolePolicies, - listRoleNamesContaining, - replaceOverrideFileWithProjectInfo, - updateApiSchema, - updateSchema, -} from 'amplify-category-api-e2e-core'; - -describe('Override table name', () => { - let projRoot: string; - let projFolderName: string; - beforeEach(async () => { - projFolderName = 'overridename'; - projRoot = await createNewProjectDir(projFolderName); - }); - - afterEach(async () => { - const metaFilePath = path.join(projRoot, 'amplify', '#current-cloud-backend', 'amplify-meta.json'); - if (fs.existsSync(metaFilePath)) { - await deleteProject(projRoot); - } - deleteProjectDir(projRoot); - }); - - it('Generates correct permissions policies for DynamoDB tables with overridden names', async () => { - const now = Math.floor(Date.now() / 1000); - const modelName = `Override${now}`; - - const schema = /* GraphQL */ ` - type ${modelName} @model { - id: ID! - content: String - } - `; - - const envName = 'integtest'; - const projName = 'overridetest'; - const cliInputsFilePath = path.join(projRoot, 'amplify', 'backend', 'api', `${projName}`, 'cli-inputs.json'); - await initJSProjectWithProfile(projRoot, { name: projName, envName }); - await addApiWithoutSchema(projRoot); - - updateSchema(projRoot, projName, schema); - expect(fs.existsSync(cliInputsFilePath)).toBe(true); - - await amplifyPush(projRoot); - - await amplifyOverrideApi(projRoot, {}); - - const overriddenTableName = `OverrideTest${now}Custom`; - const overrideCode = /* TypeScript */ ` - export function override(props: any) { - props.models['${modelName}'].modelDDBTable.tableName = '${overriddenTableName}'; - } - `; - const destOverrideFilePath = path.join(projRoot, 'amplify', 'backend', 'api', `${projName}`, 'override.ts'); - fs.writeFileSync(destOverrideFilePath, overrideCode); - - await amplifyPushOverride(projRoot); - - const meta = getProjectMeta(projRoot); - const region = meta.providers.awscloudformation.Region; - const { output } = meta.api.overridetest; - const { GraphQLAPIIdOutput, GraphQLAPIEndpointOutput, GraphQLAPIKeyOutput } = output; - const { graphqlApi } = await getAppSyncApi(GraphQLAPIIdOutput, region); - - expect(graphqlApi).toBeDefined(); - expect(graphqlApi.apiId).toEqual(GraphQLAPIIdOutput); - expect(GraphQLAPIIdOutput).toBeDefined(); - expect(GraphQLAPIEndpointOutput).toBeDefined(); - expect(GraphQLAPIKeyOutput).toBeDefined(); - - const defaultTableName = `${modelName}-${graphqlApi.apiId}-${envName}`; - const error = { message: null }; - try { - const defaultTable = await getDDBTable(defaultTableName, region); - expect(defaultTable).toBeUndefined(); - } catch (ex) { - Object.assign(error, ex); - } - expect(error).toBeDefined(); - expect(error.message).toContain(`${defaultTableName} not found`); - - const actualTable = await getDDBTable(overriddenTableName, region); - expect(actualTable).toBeDefined(); - - // Validate policy. The role will be created with the prefix {modelName}IAMRole. It should have 2 policies: one created by Amplify, one - // created by AppSync's CDK call during the `addDynamoDbDataSource` flow. We expect the policy statements for the latter to refer to the - // overridden table name. - const matchingRoleNames = await listRoleNamesContaining(modelName, region); - expect(matchingRoleNames).toBeDefined(); - expect(matchingRoleNames.length).toEqual(1); - const roleName = matchingRoleNames[0]; - - const policies = await listRolePolicies(roleName, region); - expect(policies).toBeDefined(); - expect(policies.length).toBe(2); - - const defaultPolicy = policies.find((p) => p.startsWith(`${modelName}IAMRoleDefault`)); - expect(defaultPolicy).toBeDefined(); - - const policyObject = await getRolePolicy(roleName, defaultPolicy, region); - expect(policyObject).toBeDefined(); - expect(policyObject.Statement[0].Resource[0]).toContain(overriddenTableName); - }); -}); diff --git a/packages/amplify-graphql-auth-transformer/package.json b/packages/amplify-graphql-auth-transformer/package.json index 1164184cf7..ceb3d25492 100644 --- a/packages/amplify-graphql-auth-transformer/package.json +++ b/packages/amplify-graphql-auth-transformer/package.json @@ -70,7 +70,7 @@ "coverageProvider": "v8", "coverageThreshold": { "global": { - "branches": 87, + "branches": 88, "functions": 90, "lines": 90 } diff --git a/packages/amplify-graphql-model-transformer/src/resources/dynamo-model-resource-generator.ts b/packages/amplify-graphql-model-transformer/src/resources/dynamo-model-resource-generator.ts index 363d5b5716..a348ba6dee 100644 --- a/packages/amplify-graphql-model-transformer/src/resources/dynamo-model-resource-generator.ts +++ b/packages/amplify-graphql-model-transformer/src/resources/dynamo-model-resource-generator.ts @@ -231,19 +231,7 @@ export class DynamoModelResourceGenerator extends ModelResourceGenerator { } /** - * Create a role, assumable by AppSync, with a policy statement named `DynamoDBAccess`. Policy actions are scoped to the table named - * according to Amplify's default convention (`{modelName}-{apiId}-{envName}`). - * - * In most cases, this will duplicate the `...IAMRoleDefaultPolicy` that is automatically generated by the AppSync CDK's - * `addDynamoDbDataSource` API in {@link createModelTableDataSource}. We create it anyway for 3 reasons: - * - * 1. The `...IAMRoleDefaultPolicy` respects [table name - * overrides](https://docs.amplify.aws/gen1/javascript/build-a-backend/graphqlapi/modify-amplify-generated-resources/#customize-amplify-generated-resources-for-model-directive). - * Overrides are not available to us during creation of this role. - * 2. The `DynamoDBAccess` policy is exposed as an Amplify-generated resource for overrides. Existing customers may be using this and - * depending on the policy statements - * 3. The `DynamoDBAccess` policy includes statements enabling Lambda sync flows, if enabled. Note that the sync policy does not rely on - * table name, so it does not need to be aware of table name overrides. + * createIAMRole */ createIAMRole = (context: TransformerContextProvider, def: ObjectTypeDefinitionNode, scope: Construct, tableName: string): iam.IRole => { const roleName = context.resourceHelper.generateIAMRoleName(ModelResourceIDs.ModelTableIAMRoleID(def!.name.value)); @@ -251,8 +239,8 @@ export class DynamoModelResourceGenerator extends ModelResourceGenerator { const role = new iam.Role(scope, ModelResourceIDs.ModelTableIAMRoleID(def!.name.value), { roleName, assumedBy: new iam.ServicePrincipal('appsync.amazonaws.com'), - // Use an inline policy here to prevent unnecessary policy CloudFormation resources from being generated. Note that CDK will still - // create a CFN resource for `IAMRoleDefaultPolicy` + // Use an inline policy here to prevent unnecessary policy resources from being generated + // and slowing down deployments. inlinePolicies: { DynamoDBAccess: new iam.PolicyDocument({ statements: [ @@ -308,6 +296,7 @@ export class DynamoModelResourceGenerator extends ModelResourceGenerator { ); } - return role; + // return an `IRole` to prevent modification and default policy generation. + return role.withoutPolicyUpdates(); }; }