From ccfd5085dc8fdbaf90d3a3646e8c10e26a5f583d Mon Sep 17 00:00:00 2001 From: Kaustav Ghosh Date: Thu, 8 Aug 2019 11:37:04 -0700 Subject: [PATCH] feat: narrow-down idp roles scope (#1974) * fixes based on PR comments * fix: fix async push * feat: narrow-down idp roles scope * narrow down IAM update plicies for the lambda execution role --- .../lib/push-resources.js | 20 +- .../lib/rootStackTemplate.json | 18 +- .../lib/update-idp-roles-cfn.json | 173 ++++++++++++++++++ .../src/aws-utils/aws-cfn.js | 7 +- 4 files changed, 201 insertions(+), 17 deletions(-) create mode 100644 packages/amplify-provider-awscloudformation/lib/update-idp-roles-cfn.json diff --git a/packages/amplify-provider-awscloudformation/lib/push-resources.js b/packages/amplify-provider-awscloudformation/lib/push-resources.js index 7f66d6d7f57..463244cb2ff 100644 --- a/packages/amplify-provider-awscloudformation/lib/push-resources.js +++ b/packages/amplify-provider-awscloudformation/lib/push-resources.js @@ -401,15 +401,17 @@ function uploadTemplateToS3(context, resourceDir, cfnFile, category, resourceNam function formNestedStack(context, projectDetails, categoryName, resourceName, serviceName, skipEnv) { /* eslint-enable */ const nestedStack = context.amplify.readJsonFile(`${__dirname}/rootStackTemplate.json`); - const { amplifyMeta } = projectDetails; - + let authResourceName; let categories = Object.keys(amplifyMeta); categories = categories.filter(category => category !== 'provider'); categories.forEach((category) => { const resources = Object.keys(amplifyMeta[category]); resources.forEach((resource) => { const resourceDetails = amplifyMeta[category][resource]; + if (category === 'auth') { + authResourceName = resource; + } const resourceKey = category + resource; let templateURL; if (resourceDetails.providerPlugin) { @@ -462,9 +464,23 @@ function formNestedStack(context, projectDetails, categoryName, resourceName, se } }); }); + + if (authResourceName) { + updateIdPRolesInNestedStack(context, nestedStack, authResourceName); + } return nestedStack; } +function updateIdPRolesInNestedStack(context, nestedStack, authResourceName) { + const authLogicalResourceName = `auth${authResourceName}`; + const idpUpdateRoleCfn = context.amplify.readJsonFile(`${__dirname}/update-idp-roles-cfn.json`); + + idpUpdateRoleCfn.UpdateRolesWithIDPFunction.DependsOn.push(authLogicalResourceName); + idpUpdateRoleCfn.UpdateRolesWithIDPFunctionOutputs.Properties.idpId['Fn::GetAtt'].unshift(authLogicalResourceName); + + Object.assign(nestedStack.Resources, idpUpdateRoleCfn); +} + module.exports = { run, updateStackForAPIMigration, diff --git a/packages/amplify-provider-awscloudformation/lib/rootStackTemplate.json b/packages/amplify-provider-awscloudformation/lib/rootStackTemplate.json index 95f543f08a9..1fe14f7925f 100644 --- a/packages/amplify-provider-awscloudformation/lib/rootStackTemplate.json +++ b/packages/amplify-provider-awscloudformation/lib/rootStackTemplate.json @@ -33,16 +33,11 @@ "Statement": [ { "Sid": "", - "Effect": "Allow", + "Effect": "Deny", "Principal": { "Federated": "cognito-identity.amazonaws.com" }, - "Action": "sts:AssumeRoleWithWebIdentity", - "Condition": { - "ForAnyValue:StringLike": { - "cognito-identity.amazonaws.com:amr": "authenticated" - } - } + "Action": "sts:AssumeRoleWithWebIdentity" } ] } @@ -57,16 +52,11 @@ "Statement": [ { "Sid": "", - "Effect": "Allow", + "Effect": "Deny", "Principal": { "Federated": "cognito-identity.amazonaws.com" }, - "Action": "sts:AssumeRoleWithWebIdentity", - "Condition": { - "ForAnyValue:StringLike": { - "cognito-identity.amazonaws.com:amr": "unauthenticated" - } - } + "Action": "sts:AssumeRoleWithWebIdentity" } ] } diff --git a/packages/amplify-provider-awscloudformation/lib/update-idp-roles-cfn.json b/packages/amplify-provider-awscloudformation/lib/update-idp-roles-cfn.json new file mode 100644 index 00000000000..057e01d4581 --- /dev/null +++ b/packages/amplify-provider-awscloudformation/lib/update-idp-roles-cfn.json @@ -0,0 +1,173 @@ +{ + + "UpdateRolesWithIDPFunction": { + "DependsOn": [ + "AuthRole", + "UnauthRole" + ], + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": { + "Fn::Join": [ + "\n", + [ + "const response = require('cfn-response');", + "const aws = require('aws-sdk');", + "let responseData = {};", + "exports.handler = function(event, context) {", + " try {", + " let authRoleName = event.ResourceProperties.authRoleName;", + " let unauthRoleName = event.ResourceProperties.unauthRoleName;", + " let idpId = event.ResourceProperties.idpId;", + " let promises = [];", + " let authParamsJson = { 'Version': '2012-10-17','Statement': [{'Effect': 'Allow','Principal': {'Federated': 'cognito-identity.amazonaws.com'},'Action': 'sts:AssumeRoleWithWebIdentity','Condition': {'StringEquals': {'cognito-identity.amazonaws.com:aud': idpId},'ForAnyValue:StringLike': {'cognito-identity.amazonaws.com:amr': 'authenticated'}}}]};", + " let unauthParamsJson = { 'Version': '2012-10-17','Statement': [{'Effect': 'Allow','Principal': {'Federated': 'cognito-identity.amazonaws.com'},'Action': 'sts:AssumeRoleWithWebIdentity','Condition': {'StringEquals': {'cognito-identity.amazonaws.com:aud': idpId},'ForAnyValue:StringLike': {'cognito-identity.amazonaws.com:amr': 'unauthenticated'}}}]};", + " if (event.RequestType == 'Delete') {", + " delete authParamsJson.Statement.Condition;", + " delete unauthParamsJson.Statement.Condition;", + " let authParams = { PolicyDocument: JSON.stringify(authParamsJson),RoleName: authRoleName};", + " let unauthParams = {PolicyDocument: JSON.stringify(unauthParamsJson),RoleName: unauthRoleName};", + " const iam = new aws.IAM({ apiVersion: '2010-05-08', region: event.ResourceProperties.region});", + " promises.push(iam.updateAssumeRolePolicy(authParams).promise());", + " promises.push(iam.updateAssumeRolePolicy(unauthParams).promise());", + " Promise.all(promises)", + " .then((res) => {", + " console.log(\"delete\" + res);", + " console.log(\"response data\" + JSON.stringify(res));", + " response.send(event, context, response.SUCCESS, res);", + " });", + " }", + " if (event.RequestType == 'Update' || event.RequestType == 'Create') {", + " const iam = new aws.IAM({ apiVersion: '2010-05-08', region: event.ResourceProperties.region});", + " let authParams = { PolicyDocument: JSON.stringify(authParamsJson),RoleName: authRoleName};", + " let unauthParams = {PolicyDocument: JSON.stringify(unauthParamsJson),RoleName: unauthRoleName};", + " promises.push(iam.updateAssumeRolePolicy(authParams).promise());", + " promises.push(iam.updateAssumeRolePolicy(unauthParams).promise());", + " Promise.all(promises)", + " .then((res) => {", + " console.log(\"createORupdate\" + res);", + " console.log(\"response data\" + JSON.stringify(res));", + " response.send(event, context, response.SUCCESS, {});", + " });", + " }", + " } catch(err) {", + " console.log(err.stack);", + " responseData = {Error: err};", + " response.send(event, context, response.FAILED, responseData);", + " throw err;", + " }", + "};" + ] + ] + } + }, + "Handler": "index.handler", + "Runtime": "nodejs8.10", + "Timeout": "300", + "Role": { + "Fn::GetAtt": [ + "UpdateRolesWithIDPFunctionRole", + "Arn" + ] + } + } + }, + "UpdateRolesWithIDPFunctionOutputs": { + "Type": "Custom::LambdaCallout", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "UpdateRolesWithIDPFunction", + "Arn" + ] + }, + "region": { + "Ref": "AWS::Region" + }, + "idpId": { + "Fn::GetAtt": [ + + "Outputs.IdentityPoolId" + ] + }, + "authRoleName": { + "Ref": "AuthRoleName" + }, + "unauthRoleName": { + "Ref": "UnauthRoleName" + } + } + }, + "UpdateRolesWithIDPFunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "RoleName": { + "Fn::Join": [ + "", + [ + { + "Ref": "AuthRoleName" + }, + "-idp" + ] + ] + }, + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + }, + "Action": [ + "sts:AssumeRole" + ] + } + ] + }, + "Policies": [ + { + "PolicyName": "UpdateRolesWithIDPFunctionPolicy", + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Resource": "arn:aws:logs:*:*:*" + }, + { + "Effect": "Allow", + "Action": "iam:UpdateAssumeRolePolicy", + "Resource": { + "Fn::GetAtt": [ + "AuthRole", + "Arn" + ] + } + }, + { + "Effect": "Allow", + "Action": "iam:UpdateAssumeRolePolicy", + "Resource": { + "Fn::GetAtt": [ + "UnauthRole", + "Arn" + ] + } + } + ] + } + } + ] + } + } +} \ No newline at end of file diff --git a/packages/amplify-provider-awscloudformation/src/aws-utils/aws-cfn.js b/packages/amplify-provider-awscloudformation/src/aws-utils/aws-cfn.js index 42241f985a6..ca814f611b7 100644 --- a/packages/amplify-provider-awscloudformation/src/aws-utils/aws-cfn.js +++ b/packages/amplify-provider-awscloudformation/src/aws-utils/aws-cfn.js @@ -310,7 +310,12 @@ class CloudFormation { .then((result) => { let resources = result.StackResources; resources = resources.filter(resource => - !['DeploymentBucket', 'AuthRole', 'UnauthRole'].includes(resource.LogicalResourceId)); + !['DeploymentBucket', + 'AuthRole', + 'UnauthRole', + 'UpdateRolesWithIDPFunction', + 'UpdateRolesWithIDPFunctionOutputs', + 'UpdateRolesWithIDPFunctionRole'].includes(resource.LogicalResourceId)); const promises = [];