diff --git a/.gitignore b/.gitignore index 339eaae..d2ccf21 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ src/php/wp-config.php serverless-output.yaml +samconfig.toml diff --git a/README.md b/README.md index 719a9ef..e422d06 100644 --- a/README.md +++ b/README.md @@ -2,12 +2,85 @@ Use at your own caution!!! -[Read more](https://keita.blog/?p=1796) +[Read the original article](https://keita.blog/?p=1796) -## How To Use +## Quick Start + +This creates a cloudfront domain, api gateway, lambda function, aurora (MySQL) serverless cluster and security groups in a previously provisioned VPC. + +### 0. Prerequisites + +1. AWS CLI ([download](https://aws.amazon.com/cli/)) +2. A recent version (^0.33.1) of the AWS SAM CLI with guided deployments ([download](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html)) + +### 1. Use the guided SAM template to deploy the template once. +Run the following the deploy using your default AWS profile in ~/.aws/credentials: +```bash +sam deploy -g +``` +or use the following to deploy in a custom profile: +```bash +sam deploy -g --profile REPLACE_THIS_WITH_NAMED_PROFILE +``` +| Parameter | Description | +| ------------- | ------------- | +| Stack Name | Anything you want. Must be unique from what you already have in the region unless you want to update the stack. | +| AWS Region | Region you are deploying to. | +| VpcId | The VPC in the region you are deploying to. You can obtain this by going to https://console.aws.amazon.com/vpc/home?region=`AWS_REGION_HERE`#vpcs:sort=VpcId | +| VpcSubnetIds | A comma-delimited string of subnet IDs belonging to the VPC you specified earlier. You must specify at least 1 subnet ID. | +| StageName | (optional) Stage name of API gateway. | +| CloudFrontPriceClass | (optional) See [here](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-distribution-distributionconfig.html#cfn-cloudfront-distribution-distributionconfig-priceclass) for a list of valid values. | +| DBName | (optional) The name of the database in the cluster to connect to | +| DBUser | (optional) Default: `admin`. The username of the database cluster | +| DBPassword | The password corresponding to DBUser. | +| Confirm changes before deploy | Y if you want to check changes, N otherwise. | +| Allow SAM CLI role creation | Y, to let SAM manage our deployment bucket. | +| Save arguments to samconfig.toml | Y, so you don't need to go through the steps here again later. | + +This will deploy the stack and required resources. Visiting the cloudfront domain won't work just yet. + +### 2. Set up `wp-config.php` + +Copy the modified wp-config-sample.php and replace all `cloudfront_domain_name_here` with the value of the `CloudFrontDistributionDomainName` (e.g. abcd.cloudfront.net) stack output you obtained from the previous step. + +Linux/Mac: +```bash +cp ./src/php/wp-config-sample.php ./src/php/wp-config.php +sed -i "s/cloudfront_domain_name_here/REPLACE_THIS_WITH_CLOUDFRONT_DOMAIN/g" ./src/php/wp-config.php +``` + +### 3. Redeploy ```bash -$ sam package --template-file template.yaml --output-template-file serverless-output.yaml --s3-bucket "$DEPLOY_BUCKET" -$ sam deploy --template-file serverless-output.yaml --stack-name wordpress-on-lambda --capabilities CAPABILITY_IAM -$ aws s3 sync ./src/php s3://deploy-bucket-XXXXX/prod --exclude "*.php" --exclude "*.ini" +sam deploy ``` + +We need to redeploy the updated `wp-config.php` onto our lambda function as it contains our settings. There is a circular dependency between cloudfront and the autogenerated IAM permissions for the function so placing the cloudfront domain as an environment variable won't work. + +### 4. Deploy static assets + +Replace `REPLACE_THIS_WITH_ASSETS_BUCKET_NAME` with the value of `AssetsBucketName` from the stack outputs obtained from the previous step. + +```bash +aws s3 sync ./src/php s3://REPLACE_THIS_WITH_ASSETS_BUCKET_NAME --exclude "*.php" --exclude "*.ini" +``` + +or the following if you used a named profile in step 1: + +```bash +aws s3 sync ./src/php s3://REPLACE_THIS_WITH_ASSETS_BUCKET_NAME --exclude "*.php" --exclude "*.ini" --profile REPLACE_THIS_WITH_NAMED_PROFILE +``` + +### 5. Visit cloudfront domain + +You should see the following Wordpress 5.2.1 installation (everything under `./src/php`): + +![Wordpress Install](./docs/success.png) + +## Using a different Wordpress version + +Throw everything away under ./src/php *except* for: + +- `wp-config.php` or `wp-config-sample.php` +- `php.ini` +- `src/php/wp-content/S3-Uploads-2.1.0/*` diff --git a/docs/success.png b/docs/success.png new file mode 100644 index 0000000..24db606 Binary files /dev/null and b/docs/success.png differ diff --git a/src/php/wp-config-sample.php b/src/php/wp-config-sample.php index 9b3d4c7..bfc4838 100644 --- a/src/php/wp-config-sample.php +++ b/src/php/wp-config-sample.php @@ -20,16 +20,16 @@ // ** MySQL settings - You can get this info from your web host ** // /** The name of the database for WordPress */ -define( 'DB_NAME', 'database_name_here' ); +define( 'DB_NAME', getenv('DATABASE_NAME') ); /** MySQL database username */ -define( 'DB_USER', 'username_here' ); +define( 'DB_USER', getenv('DATABASE_USER') ); /** MySQL database password */ -define( 'DB_PASSWORD', 'password_here' ); +define( 'DB_PASSWORD', getenv('DATABASE_PASS') ); /** MySQL hostname */ -define( 'DB_HOST', 'localhost' ); +define( 'DB_HOST', getenv('DATABASE_ENDPOINT') ); /** Database Charset to use in creating database tables. */ define( 'DB_CHARSET', 'utf8' ); @@ -37,9 +37,9 @@ /** The Database Collate type. Don't change this if in doubt. */ define( 'DB_COLLATE', '' ); -define('WP_SITEURL', 'https://[CLOUDFRONT DOMAIN NAME HERE]'); -define('WP_HOME', 'https://[CLOUDFRONT DOMAIN NAME HERE]'); -$_SERVER['HTTP_HOST'] = '[CLOUDFRONT DOMAIN NAME HERE]'; +define('WP_SITEURL', 'https://cloudfront_domain_name_here'); +define('WP_HOME', 'https://cloudfront_domain_name_here'); +$_SERVER['HTTP_HOST'] = 'cloudfront_domain_name_here'; define( 'S3_UPLOADS_BUCKET', getenv('S3_UPLOADS_BUCKET') . '/wp-content' ); define( 'S3_UPLOADS_USE_INSTANCE_PROFILE', true ); diff --git a/template.yaml b/template.yaml index 7b9af8c..2342e01 100644 --- a/template.yaml +++ b/template.yaml @@ -3,18 +3,43 @@ Description: WordPress on AWS Lambda! Transform: AWS::Serverless-2016-10-31 Parameters: - VpcSecurityGroupIds: - Type: 'List' - Default: 'sg-53af6735' + VpcId: + Description: The default VPC ID in the region you're deploying to. + Type: 'AWS::EC2::VPC::Id' VpcSubnetIds: Type: 'List' - Default: 'subnet-0dcd7a44,subnet-a8757ef0,subnet-b19c01d6' StageName: Type: String Default: 'Prod' CloudFrontPriceClass: Type: String + AllowedValues: + - PriceClass_100 + - PriceClass_200 + - PriceClass_All Default: 'PriceClass_200' + DBName: + Default: wordpressdb + Description: The WordPress database name. + Type: String + MinLength: '1' + MaxLength: '64' + AllowedPattern: '[a-zA-Z][a-zA-Z0-9]*' + ConstraintDescription: must begin with a letter and contain only alphanumeric characters. + DBUser: + Default: admin + Description: The WordPress database admin account username + Type: String + MinLength: '1' + MaxLength: '16' + AllowedPattern: '[a-zA-Z][a-zA-Z0-9]*' + ConstraintDescription: must begin with a letter and contain only alphanumeric characters. + DBPassword: + NoEcho: 'true' + Description: The WordPress database admin account password. The password validation regex varies for different wordpress versions. + Type: String + MinLength: '8' + MaxLength: '41' Resources: phpserver: @@ -30,11 +55,17 @@ Resources: Tracing: Active Layers: # - arn:aws:lambda:us-west-2:887080169480:layer:php73:2 - - arn:aws:lambda:us-west-2:777160072469:layer:php73:11 - + - !Sub "arn:aws:lambda:${AWS::Region}:887080169480:layer:php73:3" + Environment: Variables: S3_UPLOADS_BUCKET: !Ref assetsS3 + DATABASE_ENDPOINT: !GetAtt [dbCluster, Endpoint.Address] + DATABASE_NAME: !Ref DBName + DATABASE_USER: !Ref DBUser + # Comment out the line below or use KMS if you have requirements for security. + # Otherwise, security groups should be secure enough for networking. + DATABASE_PASS: !Ref DBPassword Policies: - VPCAccessPolicy: {} @@ -58,9 +89,25 @@ Resources: # Comment this out to run in public mode. VpcConfig: - SecurityGroupIds: !Ref VpcSecurityGroupIds + SecurityGroupIds: + - !GetAtt phpserverSecurityGroup.GroupId SubnetIds: !Ref VpcSubnetIds + phpserverSecurityGroup: + Type: AWS::EC2::SecurityGroup + Properties: + GroupDescription: >- + Security group for lambda + VpcId: !Ref VpcId + + phpserverSecurityGroupIngress: + Type: AWS::EC2::SecurityGroupIngress + DependsOn: phpserverSecurityGroup + Properties: + GroupId: !Ref phpserverSecurityGroup + IpProtocol: '-1' + SourceSecurityGroupId: !Ref phpserverSecurityGroup + restapi: Type: AWS::Serverless::Api DeletionPolicy: "Retain" @@ -78,37 +125,29 @@ Resources: DistributionConfig: Origins: - Id: PhpServer - DomainName: !Join - - '' - - - !Ref restapi - - '.execute-api.' - - !Ref AWS::Region - - '.amazonaws.com' + DomainName: !Sub "${restapi}.execute-api.${AWS::Region}.amazonaws.com" CustomOriginConfig: HTTPPort: 80 HTTPSPort: 443 OriginProtocolPolicy: https-only - OriginPath: !Join ['', ['/', !Ref StageName]] - + OriginPath: !Sub "/${StageName}" + - Id: Assets - DomainName: !Join - - "" - - - !Ref assetsS3 - - ".s3-" - - !Ref AWS::Region - - ".amazonaws.com" + DomainName: !Sub "${assetsS3}.s3-${AWS::Region}.amazonaws.com" S3OriginConfig: - OriginAccessIdentity: !Join [ '', [ 'origin-access-identity/cloudfront/', !Ref cfOriginAccessIdentity ]] + OriginAccessIdentity: !Sub "origin-access-identity/cloudfront/${cfOriginAccessIdentity}" Enabled: 'true' IPV6Enabled: 'true' HttpVersion: 'http2' PriceClass: !Ref CloudFrontPriceClass - + CustomErrorResponses: - ErrorCode: 404 ErrorCachingMinTTL: 0 + - ErrorCode: 504 + ErrorCachingMinTTL: 0 DefaultCacheBehavior: AllowedMethods: @@ -164,7 +203,7 @@ Resources: Forward: 'none' TargetOriginId: Assets ViewerProtocolPolicy: redirect-to-https - + - PathPattern: "*.jpeg" AllowedMethods: - GET @@ -249,7 +288,7 @@ Resources: assetsS3: Type: AWS::S3::Bucket - + assetsS3BucketPolicy: Type: AWS::S3::BucketPolicy Properties: @@ -259,14 +298,10 @@ Resources: - Sid: CloudFrontOrigin Effect: Allow Principal: - CanonicalUser: !GetAtt cfOriginAccessIdentity.S3CanonicalUserId + CanonicalUser: !Sub "${cfOriginAccessIdentity.S3CanonicalUserId}" Action: - s3:GetObject - Resource: !Join - - '' - - - 'arn:aws:s3:::' - - !Ref assetsS3 - - '/*' + Resource: !Sub "${assetsS3.Arn}/*" cfOriginAccessIdentity: Type: AWS::CloudFront::CloudFrontOriginAccessIdentity @@ -274,20 +309,49 @@ Resources: CloudFrontOriginAccessIdentityConfig: Comment: The Origin Access Identity to allow CloudFront to serve static files from the assets bucket (WordPress on Lambda) + dbSecurityGroup: + Type: AWS::EC2::SecurityGroup + Properties: + GroupDescription: Open database for access + SecurityGroupIngress: + - IpProtocol: tcp + FromPort: '3306' + ToPort: '3306' + SourceSecurityGroupId: !GetAtt phpserverSecurityGroup.GroupId + VpcId: !Ref VpcId + + dbCluster: + Type: AWS::RDS::DBCluster + Properties: + DBClusterIdentifier: !Join ["-", [!Ref "AWS::StackName", !Select [0, !Split ["-", !Select [2, !Split ["/", !Ref "AWS::StackId"]]]]]] + DatabaseName: !Ref DBName + MasterUsername: !Ref DBUser + MasterUserPassword: !Ref DBPassword + Engine: aurora + EngineMode: serverless + ScalingConfiguration: + AutoPause: true + MaxCapacity: 16 + MinCapacity: 1 + SecondsUntilAutoPause: 300 + VpcSecurityGroupIds: + - !GetAtt dbSecurityGroup.GroupId + Outputs: RestApiDomainName: - Value: !Join - - '' - - - !Ref restapi - - '.execute-api.' - - !Ref AWS::Region - - '.amazonaws.com' - + Value: !Sub "${restapi}.execute-api.${AWS::Region}.amazonaws.com" + CloudFrontDistributionId: Value: !Ref cloudfront CloudFrontDistributionDomainName: Value: !GetAtt cloudfront.DomainName - + AssetsBucketName: Value: !Ref assetsS3 + + DatabaseEndpoint: + Value: !GetAtt [dbCluster, Endpoint.Address] + + DatabaseName: + Value: !Ref DBName