For organizations with strict IAM controls, using the AWS-managed AdministratorAccess-Amplify
role may not be possible, due to the role having iam:CreateRole
and other write permissions. These actions are necessary because AWS Amplify creates and configures AWS services, such as Amazon Cognito, on the developer's behalf. This reduces the heavy lift in creating full-stack applications and allows the developer to focus on delivering business outcomes.
The following sections describe policies, focusing on least-privilege for IAM, that can be created to deploy this application using the Amplify CLI and AWS Identity and Access Management (IAM) permissions boundaries.
First we create an IAM policy to use as a permissions boundary. The permissions boundary policy is attached to Amplify-created roles, and intersects with the role's policies to create effective permissions. For example, if the permissions boundary allows medical-imaging:*
, and the role policy allows *.*
, the effective permissions of the role becomes medical-imaging:*
.
To enforce the attachment of the permissions boundary, we grant iam:CreateRole
, iam:AttachRolePolicy
, etc. with the condition that the iam:PermissionsBoundary
has to match our permissions boundary ARN. To prevent users from updating, detaching, or removing the permissions boundary, we set explicit denys for iam:DeleteRolePermissionsBoundary
, iam:DetachRolePolicy
, etc., for the appropriate resources and with the appropriate resources.
The permissions boundary can be created manually using the description in IAM permissions boundary policy. Alternatively, the permissions boundary policy and user access policy can be deployed together using this CloudFormation template.
The developer/user role policy can be created manually using the description in user policies section of this document. Alternatively, the permissions boundary policy and user access policy can be deployed together using this CloudFormation template.
This step requires Node 18.x
and npm
to be installed.
cd imaging-viewer-ui
npm install
Initialize the Amplify project with the IAM permissions boundary created earlier: amplify init --permissions-boundary arn:aws:iam::<Account ID>:policy/ImagingViewerPermissionsBoundary
- Enter any environment name, or press 'enter' to use the default (dev).
- Select your default editor using the arrow keys and 'enter.'
- For the authentication method,
- It is best practice to use a federated identity with short-lived credentials and NOT long-lived credentials (i.e. an IAM user).
- You must configure the AWS CLI and create a profile for your IAM role.
- Refer to your organization's guidance on exporting
AWS_ACCESS_KEY_ID
,AWS_SECRET_ACCESS_KEY
, andAWS_SESSION_TOKEN
for your role.
Add web hosting to the project: amplify add hosting
- Select
Amazon CloudFront and S3
. - Input an S3 bucket name, or use the default one. This must match the
s3:CreateBucket
policy resource in the user policy.
Publish the Amplify project: amplify publish
- Select
Yes
when prompted to continue.
Note that during the publishing step, the Amplify CLI will build the React application, and upload the result to the S3 hosting bucket. This may take up to 30 minutes. The CLI will return a CloudFront URL after a successful push.
This policy allows all actions within the AWS HealthImaging service. For the rest of this document, this policy is referred to as ImagingViewerPermissionsBoundary
.
{
"Sid": "AwsHealthImaging",
"Effect": "Allow",
"Action": [
"medical-imaging:*"
],
"Resource": "*"
}
Currently, Amplify's deployment S3 bucket does not block all public access. Amplify creates a custom CloudFormation resource that triggers a Lambda function to block all public access on this bucket.
{
"Sid": "BlockPublicAccess",
"Effect": "Allow",
"Action": "s3:PutBucketPublicAccessBlock",
"Resource": "arn:aws:s3:::amplify-*-deployment"
},
{
"Sid": "GetAmplifyStackDetails",
"Effect": "Allow",
"Action": "cloudformation:DescribeStacks",
"Resource": "arn:aws:cloudformation:*:*:stack/amplify-*"
}
Amplify creates a custom CloudFormation resource using a Lambda function to call CognitoIdentityServiceProvider.describeUserPoolClient().
{
"Sid": "AmplifyCognitoIdentityCustomResource",
"Effect": "Allow",
"Action": "cognito-idp:DescribeUserPoolClient",
"Resource": "arn:aws:cognito-idp:*:*:userpool/*"
}
Amplify creates a custom CloudFormation resource and calls iam:GetRole and iam:UpdateAssumeRolePolicy for the Cognito user pool's auth and unauth roles. Used by the Amplify base and auth stack Lambda.
{
"Sid": "AmplifyIamAuthUnauthRoles",
"Effect": "Allow",
"Action": [
"iam:GetRole",
"iam:UpdateAssumeRolePolicy"
],
"Resource": [
"arn:aws:iam::*:role/amplify-*-authRole",
"arn:aws:iam::*:role/amplify-*-unauthRole"
]
}
Amplify custom CloudFormation resources' logging.
{
"Sid": "AmplifyCustomResourceLogging",
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": [
"arn:aws:logs:*:*:*"
]
}
The following policies should be applied to the developer's IAM role.
These read-only actions allow Describe, Get, and List actions for the services below. They are used by the Amplify CLI, as outlined here.
- amplify*
- cloudformation*
- cognito-identity*
- cognito-idp*
- iam
- lambda*
- logs
- s3
* Actions include wildcards, e.g. Get*, List*, Describe*
{
"Sid": "AmplifyReadOnly",
"Effect": "Allow",
"Action": [
"amplify:Get*",
"amplify:List*",
"apigateway:Get*",
"cloudformation:Describe*",
"cloudformation:List*",
"cloudformation:Get*",
"cognito-identity:Describe*",
"cognito-identity:GetIdentityPoolRoles",
"cognito-identity:ListIdentityPools",
"cognito-idp:GetUserPoolMfaConfig",
"cognito-idp:List*",
"iam:GetPolicy",
"iam:GetRole",
"iam:GetRolePolicy",
"iam:GetUser",
"lambda:Get*",
"lambda:List*",
"logs:DescribeLogStreams",
"logs:GetLogEvents",
"s3:GetBucketLocation",
"s3:GetObject",
"s3:ListAllMyBuckets",
"s3:ListBucket",
"s3:ListBucketVersions"
],
"Resource": "*"
}
Amplify creates IAM roles for its base, authentication and function stacks. This lets Amplify run iam:CreateRole
and iam:PutRolePolicy
with limiting resource names and a PermissionBoundary condition. The Amplify application will need to be initialized with init --permissions-boundary <policy ARN>
to allow Amplify to create roles with the permissions boundary attached.
Base stack:
- amplify-*-authrole
- amplify-*-unauthRole
Auth stack:
- amplify-*-authRole-idp
- *ClientLambdaRole*
Function stack(s):
- *LambdaRole*
{
"Sid": "AmplifyCreateIamRolesWithPermissionBoundary",
"Effect": "Allow",
"Action": [
"iam:CreateRole",
"iam:PutRolePolicy"
],
"Resource": [
"arn:aws:iam::*:role/amplify-*-authRole",
"arn:aws:iam::*:role/amplify-*-authRole-idp",
"arn:aws:iam::*:role/amplify-*-unauthRole",
"arn:aws:iam::*:role/*LambdaRole*"
],
"Condition": {
"ArnLike": {
"iam:PermissionsBoundary": "arn:aws:iam::*:policy/ImagingViewerPermissionsBoundary"
}
}
}
Allow the Amplify CLI to delete roles for cleanup.
{
"Sid": "AmplifyIamDeleteRole",
"Effect": "Allow",
"Action": [
"iam:DeleteRole",
"iam:DeleteRolePolicy"
],
"Resource": [
"arn:aws:iam::*:role/amplify-*",
"arn:aws:iam::*:role/*LambdaRole*"
]
}
Allow the Amplify CLI auth stack to pass roles to Lambda. This is for the UserPoolClientInputs
Custom::LambdaCallout
resource.
{
"Sid": "AmplifyAuthLambdaPassRole",
"Effect": "Allow",
"Action": [
"iam:PassRole"
],
"Resource": [
"arn:aws:iam::*:role/*LambdaRole*"
],
"Condition": {
"StringEquals": {
"iam:PassedToService": "lambda.amazonaws.com"
}
}
}
Allow the Amplify CLI auth stack to pass roles to Cognito. This is for AWS::Cognito::IdentityPoolRoleAttachment
. Note that cognito-identity
does not support iam:PassedToService
*, hence the condition is not added.
_Tested with StringLike: iam:PassedToService: _.amazonaws.com with no success
{
"Sid": "AmplifyAuthCognitoPassRole",
"Effect": "Allow",
"Action": [
"iam:PassRole"
],
"Resource": [
"arn:aws:iam::*:role/amplify-*-authRole",
"arn:aws:iam::*:role/amplify-*-authRole-idp",
"arn:aws:iam::*:role/amplify-*-unauthRole"
]
}
Amplify-created IAM roles are tagged with keys user:Application and user:Stack.
{
"Sid": "AmplifyIamTag",
"Effect": "Allow",
"Action": [
"iam:TagRole"
],
"Resource": [
"arn:aws:iam::*:role/amplify-*",
"arn:aws:iam::*:role/*LambdaRole*"
]
}
Deny the user from deleting the permission boundary created in the template.
{
"Sid": "DenyPermissionsBoundaryDeletion",
"Effect": "Deny",
"Action": "iam:DeleteRolePermissionsBoundary",
"Resource": "*",
"Condition": {
"ArnLike": {
"iam:PermissionsBoundary": "arn:aws:iam::*:policy/ImagingViewerPermissionsBoundary"
}
}
}
Deny the user from changing the permission boundary policy.
{
"Sid": "DenyPolicyChange",
"Effect": "Deny",
"Action": [
"iam:CreatePolicyVersion",
"iam:DeletePolicyVersion",
"iam:DetachRolePolicy",
"iam:SetDefaultPolicyVersion"
],
"Resource": "arn:aws:iam::*:policy/ImagingViewerPermissionsBoundary"
}
Amplify creates an S3 bucket for deploying resources. The bucket used internally by Amplify is named amplify-*deployment. The bucket used by S3 and CloudFront defaults to --hostingbucket-, but can be overridden by the CLI during amplify add hosting
.
{
"Sid": "AmplifyS3",
"Effect": "Allow",
"Action": [
"s3:CreateBucket",
"s3:DeleteBucket",
"s3:DeleteBucketPolicy",
"s3:DeleteBucketWebsite",
"s3:DeleteObject",
"s3:DeleteObjectVersion",
"s3:PutBucketTagging",
"s3:PutBucketAcl",
"s3:PutBucketCORS",
"s3:PutBucketNotification",
"s3:PutBucketPolicy",
"s3:PutBucketWebsite",
"s3:PutEncryptionConfiguration",
"s3:PutObject",
"s3:PutObjectAcl"
],
"Resource": [
"arn:*:s3:::amplify-*-deployment",
"arn:*:s3:::amplify-*-deployment/*",
"arn:*:s3:::*hostingbucket*",
"arn:*:s3:::*hostingbucket*/*"
]
}
Amplify uses CloudFormation to deploy its resources. Allow the user to do CRUD operations on stacks, changesets and stacksets with the amplify* name.
{
"Sid": "AmplifyCloudFormation",
"Effect": "Allow",
"Action": [
"cloudformation:CreateStack",
"cloudformation:UpdateStack",
"cloudformation:DeleteStack",
"cloudformation:CreateChangeSet",
"cloudformation:ExecuteChangeSet",
"cloudformation:DeleteStackSet",
"cloudformation:CreateStackSet",
"cloudformation:UpdateStackSet"
],
"Resource": [
"arn:aws:cloudformation:*:*:stack/amplify-*"
]
}
Allow the user access to Amplify services.
{
"Sid": "AmplifyService",
"Effect": "Allow",
"Action": [
"amplify:CreateApp",
"amplify:CreateBackendEnvironment",
"amplify:CreateBranch",
"amplify:CreateDeployment",
"amplify:DeleteApp",
"amplify:DeleteBackendEnvironment",
"amplify:DeleteBranch",
"amplify:StartDeployment",
"amplify:UpdateApp"
],
"Resource": [
"*"
]
}
Allow access to Cognito Identity services for authentication via the Amplify CLI.
{
"Sid": "AmplifyCliCognitoIdentity",
"Effect": "Allow",
"Action": [
"cognito-identity:CreateIdentityPool",
"cognito-identity:DeleteIdentityPool",
"cognito-identity:SetIdentityPoolRoles",
"cognito-identity:TagResource",
"cognito-identity:UpdateIdentityPool"
],
"Resource": [
"*"
]
}
Allow access to Cognito Identity Provider services for authentication via the Amplify CLI.
{
"Sid": "AmplifyCliCognitoIdp",
"Effect": "Allow",
"Action": [
"cognito-idp:AdminAddUserToGroup",
"cognito-idp:AdminCreateUser",
"cognito-idp:CreateGroup",
"cognito-idp:CreateUserPool",
"cognito-idp:CreateUserPoolClient",
"cognito-idp:DeleteGroup",
"cognito-idp:DeleteUser",
"cognito-idp:DeleteUserPool",
"cognito-idp:DeleteUserPoolClient",
"cognito-idp:DescribeIdentityProvider",
"cognito-idp:DescribeUserPool",
"cognito-idp:DescribeUserPoolClient",
"cognito-idp:UpdateGroup",
"cognito-idp:UpdateUserPool",
"cognito-idp:UpdateUserPoolClient"
],
"Resource": [
"*"
]
}
Allow access to Lambda via the Amplify CLI. Built-in functions names start with 'amplify-*'.
{
"Sid": "AmplifyCliLambda",
"Effect": "Allow",
"Action": [
"lambda:AddLayerVersionPermission",
"lambda:AddPermission",
"lambda:CreateEventSourceMapping",
"lambda:CreateFunction",
"lambda:DeleteEventSourceMapping",
"lambda:DeleteFunction",
"lambda:DeleteLayerVersion",
"lambda:InvokeAsync",
"lambda:InvokeFunction",
"lambda:PublishLayerVersion",
"lambda:TagResource",
"lambda:RemoveLayerVersionPermission",
"lambda:RemovePermission",
"lambda:UpdateFunctionCode",
"lambda:UpdateFunctionConfiguration"
],
"Resource": [
"arn:aws:lambda:*:*:function:*"
]
}
Allow access to CloudFrnot via the Amplify CLI.
{
"Sid": "AmplifyCliCloudFront",
"Effect": "Allow",
"Action": [
"cloudfront:CreateCloudFrontOriginAccessIdentity",
"cloudfront:CreateDistribution",
"cloudfront:DeleteCloudFrontOriginAccessIdentity",
"cloudfront:DeleteDistribution",
"cloudfront:GetCloudFrontOriginAccessIdentity",
"cloudfront:GetCloudFrontOriginAccessIdentityConfig",
"cloudfront:GetDistribution",
"cloudfront:GetDistributionConfig",
"cloudfront:TagResource",
"cloudfront:UntagResource",
"cloudfront:UpdateCloudFrontOriginAccessIdentity",
"cloudfront:UpdateDistribution"
],
"Resource": "*"
}
Allow Amplify to store the deployment bucket name in SSM parameter store.
{
"Sid": "AmplifyCliSsmStore",
"Effect": "Allow",
"Action": [
"ssm:PutParameter",
"ssm:DeleteParameter"
],
"Resource": "arn:aws:ssm:*:*:parameter/amplify*"
}