AWS Setup

This guide walks you through creating an IAM user with the necessary permissions to deploy Serverless CMS.

Security Note: This guide creates an IAM user with broad permissions for deployment. For production environments, consider using more restrictive policies and separate users for different environments.

Step 1: Create IAM User

  1. Log in to the AWS Console
  2. Navigate to IAM → Users
  3. Click Create user
  4. Enter username: serverless-cms-deployer
  5. Click Next

Step 2: Attach Required Policies

The deployment user needs permissions to create and manage AWS services. You have two options:

Option A: Use Managed Policies (Simpler)

Attach these AWS managed policies to the user:

  1. AmazonS3FullAccess - For website hosting and storage
  2. AmazonDynamoDBFullAccess - For Content_Store database
  3. AWSLambda_FullAccess - For Lambda functions
  4. AmazonAPIGatewayAdministrator - For CMS_API and Public_API
  5. AmazonCognitoPowerUser - For user authentication
  6. CloudFrontFullAccess - For CDN distribution
  7. AmazonRoute53FullAccess - For DNS management
  8. AWSCertificateManagerFullAccess - For HTTPS certificates
  9. IAMFullAccess - For creating service roles
Note: Managed policies provide broader permissions than strictly necessary. For production, consider Option B with custom policies.

Option B: Create Custom Policy (Recommended for Production)

Create a custom policy with least-privilege permissions:

Policy size limit: The full permission set exceeds the AWS managed-policy size limit (6,144 characters). If the single policy below is rejected as too large, split it into the grouped managed policies described in aws-iam-policies.md and attach all of them to the deploy user (AWS allows up to 10 managed policies per user). Both representations grant the same permissions and are kept in sync.
  1. In IAM, go to Policies → Create policy
  2. Switch to the JSON tab
  3. Paste the following policy:
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "S3Management",
      "Effect": "Allow",
      "Action": [
        "s3:CreateBucket",
        "s3:DeleteBucket",
        "s3:ListBucket",
        "s3:GetBucketLocation",
        "s3:GetBucketPolicy",
        "s3:PutBucketPolicy",
        "s3:DeleteBucketPolicy",
        "s3:GetBucketVersioning",
        "s3:PutBucketVersioning",
        "s3:GetBucketWebsite",
        "s3:PutBucketWebsite",
        "s3:DeleteBucketWebsite",
        "s3:GetBucketCORS",
        "s3:PutBucketCORS",
        "s3:PutObject",
        "s3:GetObject",
        "s3:DeleteObject",
        "s3:ListBucketVersions",
        "s3:GetObjectVersion",
        "s3:DeleteObjectVersion"
      ],
      "Resource": [
        "arn:aws:s3:::*serverless-cms*",
        "arn:aws:s3:::*serverless-cms*/*"
      ]
    },
    {
      "Sid": "DynamoDBManagement",
      "Effect": "Allow",
      "Action": [
        "dynamodb:CreateTable",
        "dynamodb:DeleteTable",
        "dynamodb:DescribeTable",
        "dynamodb:UpdateTable",
        "dynamodb:PutItem",
        "dynamodb:GetItem",
        "dynamodb:DeleteItem",
        "dynamodb:Query",
        "dynamodb:Scan",
        "dynamodb:BatchWriteItem",
        "dynamodb:BatchGetItem",
        "dynamodb:UpdateTimeToLive",
        "dynamodb:DescribeTimeToLive"
      ],
      "Resource": "arn:aws:dynamodb:eu-west-2:*:table/*serverless-cms*"
    },
    {
      "Sid": "LambdaManagement",
      "Effect": "Allow",
      "Action": [
        "lambda:CreateFunction",
        "lambda:DeleteFunction",
        "lambda:GetFunction",
        "lambda:GetFunctionConfiguration",
        "lambda:UpdateFunctionCode",
        "lambda:UpdateFunctionConfiguration",
        "lambda:ListFunctions",
        "lambda:PublishVersion",
        "lambda:GetPolicy",
        "lambda:AddPermission",
        "lambda:RemovePermission",
        "lambda:TagResource",
        "lambda:InvokeFunction"
      ],
      "Resource": "arn:aws:lambda:eu-west-2:*:function:*serverless-cms*"
    },
    {
      "Sid": "APIGatewayManagement",
      "Effect": "Allow",
      "Action": ["apigateway:*"],
      "Resource": "arn:aws:apigateway:eu-west-2::/*"
    },
    {
      "Sid": "CognitoManagement",
      "Effect": "Allow",
      "Action": [
        "cognito-idp:CreateUserPool",
        "cognito-idp:DeleteUserPool",
        "cognito-idp:DescribeUserPool",
        "cognito-idp:UpdateUserPool",
        "cognito-idp:CreateUserPoolClient",
        "cognito-idp:DeleteUserPoolClient",
        "cognito-idp:DescribeUserPoolClient",
        "cognito-idp:UpdateUserPoolClient",
        "cognito-idp:CreateGroup",
        "cognito-idp:DeleteGroup",
        "cognito-idp:AdminCreateUser",
        "cognito-idp:AdminDeleteUser",
        "cognito-idp:AdminAddUserToGroup",
        "cognito-idp:ListUsers",
        "cognito-idp:ListGroups"
      ],
      "Resource": "arn:aws:cognito-idp:*:*:userpool/*"
    },
    {
      "Sid": "CloudFrontManagement",
      "Effect": "Allow",
      "Action": [
        "cloudfront:CreateDistribution",
        "cloudfront:DeleteDistribution",
        "cloudfront:GetDistribution",
        "cloudfront:UpdateDistribution",
        "cloudfront:ListDistributions",
        "cloudfront:CreateInvalidation",
        "cloudfront:CreateOriginAccessControl",
        "cloudfront:ListOriginAccessControls",
        "cloudfront:GetOriginAccessControl",
        "cloudfront:DeleteOriginAccessControl",
        "cloudfront:CreateFunction",
        "cloudfront:PublishFunction",
        "cloudfront:DescribeFunction"
      ],
      "Resource": "*"
    },
    {
      "Sid": "Route53Management",
      "Effect": "Allow",
      "Action": [
        "route53:ListHostedZones",
        "route53:GetHostedZone",
        "route53:ListResourceRecordSets",
        "route53:ChangeResourceRecordSets",
        "route53:GetChange"
      ],
      "Resource": "*"
    },
    {
      "Sid": "ACMManagement",
      "Effect": "Allow",
      "Action": [
        "acm:RequestCertificate",
        "acm:DeleteCertificate",
        "acm:DescribeCertificate",
        "acm:ListCertificates",
        "acm:AddTagsToCertificate"
      ],
      "Resource": "*"
    },
    {
      "Sid": "IAMRoleManagement",
      "Effect": "Allow",
      "Action": [
        "iam:CreateRole",
        "iam:DeleteRole",
        "iam:GetRole",
        "iam:TagRole",
        "iam:PassRole",
        "iam:UpdateAssumeRolePolicy",
        "iam:AttachRolePolicy",
        "iam:DetachRolePolicy",
        "iam:PutRolePolicy",
        "iam:DeleteRolePolicy",
        "iam:GetRolePolicy"
      ],
      "Resource": [
        "arn:aws:iam::*:role/*serverless-cms*",
        "arn:aws:iam::*:role/*-exec-role",
        "arn:aws:iam::*:policy/*serverless-cms*"
      ]
    },
    {
      "Sid": "CloudWatchLogs",
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogGroup",
        "logs:DeleteLogGroup",
        "logs:DescribeLogGroups",
        "logs:PutRetentionPolicy"
      ],
      "Resource": "arn:aws:logs:eu-west-2:*:log-group:/aws/lambda/*serverless-cms*"
    }
  ]
}
  1. Click Next
  2. Name the policy: ServerlessCMSDeploymentPolicy
  3. Click Create policy
  4. Return to user creation and attach this custom policy

Step 3: Create Access Keys

  1. After creating the user, go to the Security credentials tab
  2. Scroll to Access keys section
  3. Click Create access key
  4. Select Command Line Interface (CLI)
  5. Check the confirmation box
  6. Click Next
  7. Add description tag: Serverless CMS deployment
  8. Click Create access key
Critical: Download the CSV or copy both the Access Key ID and Secret Access Key immediately. You won't be able to see the secret key again. Store these securely in a password manager.

Step 4: Configure AWS CLI

Configure the AWS CLI with your new credentials using a named profile:

aws configure --profile cms-deploy

Enter the following when prompted:

AWS Access Key ID [None]: YOUR_ACCESS_KEY_ID
AWS Secret Access Key [None]: YOUR_SECRET_ACCESS_KEY
Default region name [None]: eu-west-2
Default output format [None]: json
Profile Name: The profile name cms-deploy matches the configuration in src/main/serverless/config.json. Using this name ensures the deployment commands work without additional configuration.

Step 5: Verify Configuration

Test that your credentials work correctly:

aws sts get-caller-identity --profile cms-deploy

You should see output similar to:

{
  "UserId": "AIDAXXXXXXXXXXXXXXXXX",
  "Account": "123456789012",
  "Arn": "arn:aws:iam::123456789012:user/serverless-cms-deployer"
}

If you see an error, verify:

  • Access keys were entered correctly
  • Profile name is cms-deploy
  • Region is set to eu-west-2

Step 6: Set Up Route 53 Hosted Zone

Before deploying, ensure you have a Route 53 hosted zone for your root domain:

Create Hosted Zone

  1. Navigate to Route 53 → Hosted zones
  2. Click Create hosted zone
  3. Enter your domain name (e.g., example.com)
  4. Select Public hosted zone
  5. Click Create hosted zone

Update Domain Nameservers

After creating the hosted zone, you'll see 4 nameservers (NS records). Update your domain registrar to use these nameservers:

  1. Copy the 4 nameserver addresses from the NS record
  2. Log in to your domain registrar (GoDaddy, Namecheap, etc.)
  3. Find the nameserver settings for your domain
  4. Replace the existing nameservers with the AWS nameservers
  5. Save changes
DNS Propagation: Nameserver changes can take up to 48 hours to propagate globally, though they typically complete within a few hours. You can proceed with deployment, but SSL certificate validation may be delayed until DNS propagates.

Verify DNS Configuration

After nameserver propagation, verify DNS is working:

dig NS example.com +short

You should see the AWS nameservers listed.

Security Best Practices

Enable MFA

Add multi-factor authentication to the IAM user for additional security:

  1. Go to IAM → Users → serverless-cms-deployer
  2. Click Security credentials tab
  3. Under Multi-factor authentication (MFA), click Assign MFA device
  4. Follow the setup wizard

Rotate Access Keys

Regularly rotate access keys (every 90 days recommended):

  1. Create a new access key
  2. Update AWS CLI configuration with new key
  3. Test deployment with new key
  4. Delete old access key

Use Secrets Manager (Optional)

For CI/CD pipelines, store credentials in AWS Secrets Manager instead of environment variables:

# Store credentials
aws secretsmanager create-secret \
  --name serverless-cms/deploy-credentials \
  --secret-string '{"accessKeyId":"YOUR_KEY","secretAccessKey":"YOUR_SECRET"}' \
  --profile cms-deploy
AWS Setup Complete! Proceed to Domain Configuration to configure your domain settings in the project.