The relevant risks are accidental public database access, leaked credentials, compromised WordPress code/plugins, cross-user cache leakage, malicious upload execution, unauthorized deployment, instance-level compromise, and account lockout or uncontrolled cost during a demonstration. The site is a fictional business and collects no enquiries or real customer data.
The working AWS administrator, root recovery, unrelated account infrastructure, and CodeConnections ownership stay outside this project's destructive scope. Terraform creates project service roles; it does not delete an IAM user, create permanent access keys, change the account plan, or join an organization.
| Area | Control | Verification needed |
|---|---|---|
| Public ingress | CloudFront managed source ranges plus a per-deployment origin header; default ALB response 403 | Direct-origin denial and public path test |
| Private compute | No public EC2 IP, no SSH rule, IMDSv2 required with hop limit 1 | Launch-template/instance inspection |
| Database | Private subnet group, explicit two-role ingress, encrypted gp3, TLS required | Public accessibility check and real TLS health query |
| EFS | Encryption at rest; mount helper TLS + IAM; access point and SG restriction | Mount/permission check and upload continuity |
| App privileges | Dedicated OS user; read-only source; writeable shared uploads only | File mode and upload-write checks |
| Secrets | RDS-managed master password; separate app and initial admin secrets; atomic runtime refresh | Actual role-denial tests and rotation rehearsal |
| WordPress | Disabled file editing/modification and automatic code updates; XML-RPC disabled; HTTPS admin/cookies; same stable salts across nodes | CMS/admin behavior and security header checks |
| Uploads | Nginx denies PHP execution in uploads; authenticated WordPress upload handling | Benign denied-extension test |
| Caching | Dynamic WordPress pages/admin/cookies uncached; upload-specific behavior | Two-user cache/session separation check |
| Delivery | Separate quality/initialization/deployment/verification roles; scoped SSM rolling traffic control and restoration; optional CodeDeploy and production approval | Failed build and failed release exercise |
| Logging | No credential payload output; limited access logs omit query strings and cookies; bounded retention | Review actual log contents before screenshots/publication |
| Source control | Ignores for secrets/state; high-confidence secret checks; dependency audit; no AWS credentials in Actions | Review tracked files and actual CI outcomes |
Application instance role: reads only its application secret and project artifact objects; mounts/writes only the project EFS access point; writes its application log streams and the CWAgent metric namespace. The AWS-managed AmazonSSMManagedInstanceCore policy supplies the supported Systems Manager agent permissions. No master-database or initial-admin secret ARN is granted to the instance role.
Quality build role: accesses project artifacts and pipeline logs. It has no database network placement and no Secrets Manager grant. Source changes are tested before initialization/deployment.
Initialization build role: reads the RDS-managed master, app, and initial-admin secret; writes only the two application-owned secret containers; connects to the project database from its designated security group. EC2 network-interface lifecycle/describe permissions are required by VPC-enabled CodeBuild; supported subnet/service conditions restrict the network-interface permission. The SQL grant is limited to wordpress.*, not global MySQL privileges. This role remains privileged within that database bootstrap boundary, so protecting the repository and pipeline approval path matters.
Verification role: reads its release artifact/log context and can invalidate the specific project distribution. It does not retrieve application secrets.
SSM deployment build role: reads this project's pipeline artifacts and writes only its cloudpress-releases/* prefix. It can send only the AWS shell document to EC2 instances carrying the CloudPress project tag, environment tag, and exact ASG membership tag. It can register/deregister targets only in the project target group and suspend/resume processes only in the project ASG. Required inventory/status APIs have service-imposed broader read scope. SSM shell access is root-level access on those permitted hosts, so this role is a privileged deployment identity, not a low-privilege website identity. It cannot retrieve database secrets directly. The running application role reads only the SSM release prefix when SSM delivery is selected.
CodePipeline role: can use the selected CodeConnections connection, manipulate project artifacts, and invoke only the configured quality/initialization/deployment/verification builds. Optional CodeDeploy permissions are present only when that driver is explicitly selected.
Optional CodeDeploy service role: uses AWS's AWSCodeDeployRole managed policy for supported ASG/lifecycle/load-balancer integration only when selected. This policy is broader than a handwritten action-by-action project policy. The live Free-plan account rejected CodeDeploy application creation and no plan upgrade was performed; do not claim that path was deployed successfully. Production review should verify actual effective policies and the selected driver.
- RDS generates/manages its master password in Secrets Manager. Terraform receives only its ARN; no
passwordoraws_secretsmanager_secret_versioncontaining a credential is created by Terraform. - The private initialization build generates a separate application database password and stable WordPress salts with Python's cryptographic random generator. It passes AWS CLI JSON through a temporary file with mode
0600inside a0700directory, then removes it on success or failure. Only the private file path enters process arguments. This replaced stdin after the live CLI rejected repeated reads from/dev/stdin. - I generated the first WordPress administrator credential into a separate secret. Only the initialization role and my administrative access can retrieve it. It is never printed or included in an artifact.
- SQL initialization uses the master secret only in the private build process. WordPress uses its database-scoped application user.
- EC2 writes a filtered secret to
/run/cloudpress/secret.json, root-owned with the application group and mode 0640, using an atomic rename. The parent directory is mode 0750. Refresh runs every five minutes and retains the prior file if retrieval/validation fails. - A restarted host recreates runtime state through its saved refresh service. A new host receives the approved release and retrieves the current application secret using its own instance-role session.
Application-password rotation is not fully automated by this repository. A reviewed rotation changes the stored application password and database user coherently, validates all hosts, and accounts for the refresh window. Master-password rotation is independently managed by RDS and does not require using that master password for ordinary website requests. Do not rotate WordPress salts casually: it invalidates existing login sessions.
The origin access header is a secret stored in Terraform state because both ALB and CloudFront need its value. It is not a database password. Protect state with encryption, access control, versioning/locking and private review plans; never commit state, plan output containing sensitive values, or a console screenshot exposing this header.
Default-hostname lab mode gives visitor HTTPS but uses an HTTP CloudFront-to-ALB origin leg and the CloudFront default certificate policy. Production mode requires a custom hostname and validated ACM certificate, with a modern viewer policy and HTTPS on that origin leg. Both modes use private HTTP from ALB to Nginx. RDS connections and EFS mounts use TLS. The RDS CA bundle is installed on the host; the initialization client explicitly validates certificate and hostname. Confirm the deployed PHP/MySQL TLS behavior rather than inferring it from a flag alone.
WordPress authentication uses core password/cookie/nonces behavior and the generated initial admin password. Application MFA, a WAF rule set, rate limiting, and an upstream identity-aware admin gateway are not implemented. Before handling real customers, add/test the appropriate controls and incident process. The initial administrator secret should be retrieved privately, then I would change the login credential and record the new secret lifecycle.
- Confirm effective IAM with actual access-denial tests, including inability of EC2 to read the master/admin secret.
- Protect the source branch and require reviewed changes before they reach the privileged initializer. The Actions workflow has no AWS credentials and is supplemental to AWS-native deployment.
- Validate signed/pinned OS images and package promotion. Application/tool versions are pinned, but the AL2023 AMI SSM parameter and supported AWS agent installer follow current upstream releases.
- Verify SELinux policy/context behavior on the deployed OS before claiming enforcement. No blanket SELinux-disable command is included.
- Exercise restores from RDS and EFS backups and document achieved recovery times; configuration is not a restore test.
- The operations SNS topic has no subscriber configured by this repository. No notification delivery is claimed. Its AWS-managed SNS encryption key must be reviewed for the chosen AWS-service publisher before delivery is enabled; a customer-managed key policy may be required for CloudWatch alarm publishing.
- Use a dedicated key/publisher policy if encrypted operational notifications require it; do not add a paid notification/security service without reviewing the account's Free-plan/credit constraints.
- Review WordPress/plugin dependencies and patch on a controlled schedule. Disabling direct file modification means maintenance must occur through a tested release.
The implementation uses RDS-managed master credentials, CodeConnections GitHub integration, and CodeDeploy load-balancer traffic control. These references explain supported mechanisms; they do not substitute for deployment evidence or a completed security assessment.