Using AWS Secrets Manager for Wordpress Configuration (wp-config.php)

Posted on 06 Apr 2020 by Ray Heffer

AWS Secrets Manager allows you to protect critical information for your applications such as passwords, secret keys, and salts. Rather than storing these locally on an EC2 instance (or worse), including them in your code risking them getting leaked on public repositories, you can now use the AWS Secrets Manager API. In fact you can use it to store anything that you want to keep away from prying eyes. When learning more about AWS Secrets Manager, my first thought was how to use this with Wordpress.

The wp-config.php file in Wordpress contains the keys to the kingdom. With most deployments, this file contains the database hostname, username, password, salts, and hashes. If a hacker gains access to this file, it’s game over. One best practice is to place the the Wordpress configuration file one level up, so it cannot be directly accessed using a browser. But that isn’t always going to keep the contents of the file secure. If for some reason, PHP fails on the web host, such as a botched patch or upgrade, there is a potential that PHP files are rendered as text.

The main concern are vulnerabilities with Wordpress plugins. For example, in 2015, an exploit was found in the Slider Revolution (revslider) plug-in, that allowed attackers to access wp-config.php, among other critical files on the web server, by manipulating the URL (action=revslider_show_image&img=../wp-config.php) to gain access. By the way, this is something you can also protect against with AWS Firewall Manager and AWS WAF rules. So despite the file being stored one level up, this vulnerability allowed attackers to simply access it by manipulating the URL. I provide guidance on hardening a Wordpress server in my previous blog and ebook on building a secure Wordpress server with CentOS 7.

Wordpress on Amazon EC2

To perform integration of AWS Secrets Manager with Wordpress, we will use an AWS EC2 instance running Amazon Linux 2, and a bootstrap (User data) script to install Wordpress and the packages required to support this configuration.

Pre-Requisites

All you need to get started is Wordpress installed and configured. You can deploy an EC2 instance, connect Wordpress to Amazon RDS (MySQL), and do a basic installation for testing.

You can use the following script to get an Amazon Linux 2 instance running quickly with Wordpress. This will update the OS, download and install Wordpress (to /var/www/html), install PHP, MariaDB, and get you to a state where you can run the initial setup steps for Wordpress.

#!/bin/bash
yum update -y
cd /tmp
wget https://wordpress.org/latest.tar.gz
tar xvf latest.tar.gz
yum install -y httpd mariadb-server php-pecl-mcrypt php-pecl-imagick php-mbstring
amazon-linux-extras install -y php7.3
systemctl enable mariadb
systemctl start mariadb
systemctl enable httpd
systemctl start httpd
rsync -r /tmp/wordpress/. /var/www/html
chown -R apache:apache /var/www/

You can keep this instance or terminate it later if you’d prefer. This initial instance will be used for the setup and testing to get our Wordpress database installed. Once you have Wordpress up and running, you will need some additional packages to allow environment variables (read from AWS Secrets Manager) to be interpreted by the wp-config.php configuration file.

  • Composer is a PHP package manager, which is used to deploy
  • PHP dotenv by Vance Lucas.
  • jq is a JSON command line tool used to parse and copy the data from AWS Secrets Manager into an environment variables file (‘.env’).

1. Install Composer

The following steps will download the Composer installer to /tmp, then move it to /usr/local/bin/composer, and then create a symbolic link from /usr/bin/composer. This will ensure you can run the composer command globally.

cd /tmp
curl -sS https://getcomposer.org/installer | sudo php
mv composer.phar /usr/local/bin/composer
ln -s /usr/local/bin/composer /usr/bin/composer

2. Install PHP DotEnv

cd /var/www/
wget https://raw.githubusercontent.com/rayheffer/wp-secrets/master/composer.json
composer install
chown -R apache:apache /var/www/

3. Install jq

yum install -y jq

Retrieving Secrets

The following command will connect to AWS Secrets Manager, parse the data, and store in /var/www/.env, which is one level above your website directory (/var/www/html). Make sure to replace REGION, with your AWS region, and enter the Secret ID you are using with AWS Secrets Manager. aws secretsmanager get-secret-value --secret-id **SECRETID** --query SecretString --version-stage AWSCURRENT --region **REGION** --output text | jq -r 'to_entries|map("\(.key)=\(.value|tostring)")|.[]' > /var/www/.env

Wordpress Configuration

The final step is to update wp-config.php with the environment variables that PHP dotenv has read from the local .env file in (/var/www/). Feel free to use my wp-config.php file, which is on GitHub, otherwise be sure to include the following:

1. Load PHP dotenv into wp-config.php

You need to place the following code at the top of your wp-config.php. It will come immediately after <?php on the next line. Because wp-config.php is in /var/www/, this checks the current directory. If wp-config.php resides in /var/www/html/, which isn’t recommended, then this will need to be modified (E.g. /../vendor/autoload.php and (__DIR__.'/../')). If wp-config.php in in /var/www/ then the following code will be unchanged.

if(file_exists(__DIR__ . '/vendor/autoload.php')) {
require_once __DIR__ . '/vendor/autoload.php';
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
$dotenv->load();
}
if(file_exists(dirname(__DIR__) . '/vendor/autoload.php')) {
require_once dirname(__DIR__) . '/vendor/autoload.php';
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
$dotenv->load();
}

2. Replace Secrets with Environment Variables

Next, where the database settings are defined, you need to change this to read from the environment variable that PHP dotenv has loaded.

define( 'DB_NAME', getenv('dbname'));
define( 'DB_USER', getenv('username'));
define( 'DB_PASSWORD', getenv('password'));
define( 'DB_HOST', getenv('host'));

3. Use Environment Variables for Keys and Salts

The next step is to store your secret keys and salts in AWS Secrets Manager. Now is also a good time to regenerate these! If hackers gain access to secret keys, then your site can be compromised regardless of whether you change your database credentials. Due to the fact that these might contain special characters (' / : \), this might not get interpreted correctly, so use a string of uppercase and lowercase characters and numbers, for each one.

define( 'AUTH_KEY',         getenv('AUTH_KEY') );
define( 'SECURE_AUTH_KEY',  getenv('SECURE_AUTH_KEY') );
define( 'LOGGED_IN_KEY',    getenv('LOGGED_IN_KEY') );
define( 'NONCE_KEY',        getenv('NONCE_KEY') );
define( 'AUTH_SALT',        getenv('AUTH_SALT') );
define( 'SECURE_AUTH_SALT', getenv('SECURE_AUTH_SALT') );
define( 'LOGGED_IN_SALT',   getenv('LOGGED_IN_SALT') );
define( 'NONCE_SALT',       getenv('NONCE_SALT') );

Example (do not use):

This is what you would need to store in AWS Secrets Manager. Please generate your own hashes

AUTH_KEY=RV4EWtFfgphw6ZQjUFRL6nSntQPnXJpYRVbz8Qd3
SECURE_AUTH_KEY=rWdFu7zkXQT2fv3FzjUtwwD3pbyzVwgHft2VkCDV
LOGGED_IN_KEY=mSH5XGBG82RsCvo2ZTXndJBttaZoc4eArj8xMBfg
NONCE_KEY=7zvWCXQqBvo7oR88CDd5c9tk3pyE7guqRBdsMtYD
AUTH_SALT=vzVUdLi7QBXyxr6mKCbdgaKAwausGqQyxTjY3aRt
SECURE_AUTH_SALT=c7ut68hmtpAoKVfk5sYXYWgUxDp2xcBSFrnKrVjE
LOGGED_IN_SALT=aQAgHoTkk8F4cSMtX3zKvC659vuEbsTRqZiw5gbq
NONCE_SALT=e7BuinvPydPNfsoKJfoQEtHQSwbkGASfew4FnSkK

User Data Script (Auto Install Bootstrap)

If you are setting up a new EC2 instance, you can use this script to install everything from scratch. This is ideal if you want to setup an Auto Scaling group behind a load-balancer, connecting to an existing Wordpress database. The scripts used in this blog post can also be downloaded from my GitHub repo: https://github.com/rayheffer/wp-secrets.

#!/bin/bash
yum update -y
cd /tmp
wget https://wordpress.org/latest.tar.gz
tar xvf latest.tar.gz
yum install -y httpd mariadb-server jq
amazon-linux-extras install -y php7.3
yum install -y php-pecl-mcrypt php-pecl-imagick php-mbstring
systemctl enable mariadb
systemctl start mariadb
systemctl enable httpd
systemctl start httpd
rsync -r /tmp/wordpress/. /var/www/html
aws secretsmanager get-secret-value --secret-id WordpressDB --query SecretString --version-stage AWSCURRENT --region us-east-1 --output text | jq -r 'to_entries|map("\(.key)=\(.value|tostring)")|.[]' > /var/www/.env
curl -sS https://getcomposer.org/installer | sudo php
mv composer.phar /usr/local/bin/composer
ln -s /usr/local/bin/composer /usr/bin/composer
cd /var/www/
wget https://raw.githubusercontent.com/rayheffer/wp-secrets/master/wp-config.php
wget https://raw.githubusercontent.com/rayheffer/wp-secrets/master/composer.json
sudo composer install
chown -R apache:apache /var/www/

Note: The Composer installation in the user data script uses Sudo. Usually commands in user data are run as root, but the Composer installation needs to be run as Sudo.

Conclusion

As you can see, using AWS Secrets Manager provides an easy and effective way to keep your database credentials, secret access keys, and salts, out of the wp-config.php file. One of the advantages of doing this is that if you want to deploy a new EC2 instance, using the script I shared above, you can deploy a new web server in a matter of minutes.