diff --git a/lamp-ubuntu22.sh b/lamp-ubuntu22.sh index 78ede35..91ece94 100644 --- a/lamp-ubuntu22.sh +++ b/lamp-ubuntu22.sh @@ -165,6 +165,10 @@ REPLACE=${REPLACE//$'\n'/\\n} # Escape the new line characters REPLACE=${REPLACE//\$/\\$} # Escape the $ characters perl -pi -e "s/$FIND/$REPLACE/m" /etc/apache2/apache2.conf +printf "Generating AES-256-CBC encryption key\n" +ENCRYPTION_KEY=$(openssl rand -base64 32) +IV=$(openssl rand -base64 16) + printf "Adding configuration for /srv/www...\n" FIND="#<\/Directory>" REPLACE="$(cat << 'EOF' @@ -181,6 +185,8 @@ REPLACE="$(cat << 'EOF' Header set X-Frame-Options sameorigin Header unset X-Powered-By Header set X-XSS-Protection "1; mode=block" + SetEnv WPCONFIG_ENCKEY ENCRYPTION_KEY + SetEnv WPCONFIG_IV ENCRYPTION_IV # Disable unused HTTP request methods @@ -189,6 +195,9 @@ REPLACE="$(cat << 'EOF' EOF )" +# Replace the placeholders with actual values +REPLACE=${REPLACE//ENCRYPTION_KEY/$ENCRYPTION_KEY} +REPLACE=${REPLACE//ENCRYPTION_IV/$IV} REPLACE=${REPLACE//\//\\\/} # Escape the / characters REPLACE=${REPLACE//$'\n'/\\n} # Escape the new line characters perl -pi -e "s/$FIND/$REPLACE/m" /etc/apache2/apache2.conf @@ -319,6 +328,41 @@ while true; do esac done + +# Suggest stg and dev domains +stg_domain="stg.$domain" +dev_domain="dev.$domain" + +echo "Suggested staging domain: $stg_domain" +while true; do + read -p "Would you like to keep this as your staging domain? [Y/n] " answer + case "$answer" in + [Yy]*|"") # Accept default or yes + break;; + [Nn]* ) # Enter a new value + read -p "Enter your staging domain: " stg_domain + break;; + * ) echo "Please answer yes or no.";; + esac +done + +echo "Suggested development domain: $dev_domain" +while true; do + read -p "Would you like to keep this as your development domain? [Y/n] " answer + case "$answer" in + [Yy]*|"") # Accept default or yes + break;; + [Nn]* ) # Enter a new value + read -p "Enter your development domain: " dev_domain + break;; + * ) echo "Please answer yes or no.";; + esac +done + +printf "Main domain: $domain\n" +printf "Staging domain: $stg_domain\n" +printf "Development domain: $dev_domain\n" + # Get IPv4 IPV4=$(ip -4 addr | grep inet | grep -v '127.0.0.1' | awk -F '[ \t]+|/' '{print $3}' | grep -v ^127.2.1) @@ -327,6 +371,14 @@ if [ -f /etc/apache2/sites-available/$domain.conf ]; then printf "Backing up existing virtual host configuration file to /etc/apache2/sites-available/$domain.conf.bak\n" cp /etc/apache2/sites-available/$domain.conf /etc/apache2/sites-available/$domain.conf.bak fi +if [ -f /etc/apache2/sites-available/$stg_domain.conf ]; then + printf "Backing up existing virtual host configuration file to /etc/apache2/sites-available/$stg_domain.conf.bak\n" + cp /etc/apache2/sites-available/$stg_domain.conf /etc/apache2/sites-available/$stg_domain.conf.bak +fi +if [ -f /etc/apache2/sites-available/$dev_domain.conf ]; then + printf "Backing up existing virtual host configuration file to /etc/apache2/sites-available/$dev_domain.conf.bak\n" + cp /etc/apache2/sites-available/$dev_domain.conf /etc/apache2/sites-available/$dev_domain.conf.bak +fi # Production VIRTUALHOST=" @@ -335,28 +387,43 @@ VIRTUALHOST=" DocumentRoot /srv/www/$domain/public_html/ ErrorLog /srv/www/$domain/logs/error.log CustomLog /srv/www/$domain/logs/access.log combined + SetEnv WPCONFIG_ENVNAME production \n"; echo -e "$VIRTUALHOST" > /etc/apache2/sites-available/$domain.conf +# Staging +VIRTUALHOST=" + ServerName $stg_domain + DocumentRoot /srv/www/$stg_domain/public_html/ + ErrorLog /srv/www/$stg_domain/logs/error.log + CustomLog /srv/www/$stg_domain/logs/access.log combined + SetEnv WPCONFIG_ENVNAME staging +\n"; +echo -e "$VIRTUALHOST" > /etc/apache2/sites-available/$stg_domain.conf + # Development VIRTUALHOST=" - ServerName dev.$domain - DocumentRoot /srv/www/dev.$domain/public_html/ - ErrorLog /srv/www/dev.$domain/logs/error.log - CustomLog /srv/www/dev.$domain/logs/access.log combined + ServerName $dev_domain + DocumentRoot /srv/www/$dev_domain/public_html/ + ErrorLog /srv/www/$dev_domain/logs/error.log + CustomLog /srv/www/$dev_domain/logs/access.log combined + SetEnv WPCONFIG_ENVNAME development \n"; -echo -e "$VIRTUALHOST" > /etc/apache2/sites-available/dev.$domain.conf +echo -e "$VIRTUALHOST" > /etc/apache2/sites-available/$dev_domain.conf # Create directories mkdir -p /srv/www/$domain/public_html mkdir -p /srv/www/$domain/logs -mkdir -p /srv/www/dev.$domain/public_html -mkdir -p /srv/www/dev.$domain/logs +mkdir -p /srv/www/$stg_domain/public_html +mkdir -p /srv/www/$stg_domain/logs +mkdir -p /srv/www/$dev_domain/public_html +mkdir -p /srv/www/$dev_domain/logs chown -R www-data:www-data /srv/www # Enable sites a2ensite $domain -a2ensite dev.$domain +a2ensite $stg_domain +a2ensite $dev_domain service apache2 reload # PHP @@ -524,6 +591,28 @@ while true; do * ) break;; esac done +while true; do + printf "\n" + read -p "Staging database name (recommended: use domain without TLD followed by _stg, for mydomain.com use mydomain_stg): " stgdbname + case $stgdbname in + "" ) printf "Database name may not be left blank\n";; + * ) break;; + esac +done +while true; do + read -p "Staging database user (recommended: use same as database name, max 16 characters): " stgdbuser + case $stgdbuser in + "" ) printf "User name may not be left blank\n";; + * ) break;; + esac +done +while true; do + read -sp "Staging database password: " stgdbpass + case $stgdbpass in + "" ) printf "\nPassword may not be left blank\n";; + * ) break;; + esac +done while true; do printf "\n" read -p "Development database name (recommended: use domain without TLD followed by _dev, for mydomain.com use mydomain_dev): " devdbname @@ -547,18 +636,52 @@ while true; do esac done -printf "Create database $dbname...\n" -mysql -u root -p$mysqlrootpsw -e "CREATE DATABASE $dbname;" -printf "Create user $dbuser...\n" -mysql -u root -p$mysqlrootpsw -e "CREATE USER '$dbuser'@localhost IDENTIFIED BY '$dbpass';" -printf "Grant $dbuser all privileges on $dbname...\n" -mysql -u root -p$mysqlrootpsw -e "GRANT ALL PRIVILEGES ON $dbname.* TO '$dbuser'@localhost;" -printf "Create database $devdbname...\n" -mysql -u root -p$mysqlrootpsw -e "CREATE DATABASE $devdbname;" -printf "Create user $devdbuser...\n" -mysql -u root -p$mysqlrootpsw -e "CREATE USER '$devdbuser'@localhost IDENTIFIED BY '$devdbpass';" -printf "Grant $devdbuser all privileges on $devdbname...\n" -mysql -u root -p$mysqlrootpsw -e "GRANT ALL PRIVILEGES ON $devdbname.* TO '$devdbuser'@localhost;" +# Declare associative arrays for each environment +declare -A production=( [dbname]=$dbname [dbuser]=$dbuser [dbpass]=$dbpass [key]="DB_" ) +declare -A staging=( [dbname]=$stgdbname [dbuser]=$stgdbuser [dbpass]=$stgdbpass [key]="STGDB_" ) +declare -A development=( [dbname]=$devdbname [dbuser]=$devdbuser [dbpass]=$devdbpass [key]="DEVDB_" ) + +# Array of all environments +environments=(production staging development) + +printf $environments + +# File to store encrypted credentials +encrypted_credentials_file="encrypted_credentials.txt" +key_hex=$(echo -n $ENCRYPTION_KEY | base64 -d | xxd -p -c 32) +iv_hex=$(echo -n $IV | base64 -d | xxd -p -c 16) + +# Loop through each environment +for env in "${environments[@]}"; do + # Dynamically access the associative array for the current environment + declare -n current="${env}" + + printf "Create database ${current[dbname]}...\n" + mysql -u root -p"$mysqlrootpsw" -e "CREATE DATABASE ${current[dbname]};" + + # Encrypt database name + value="${current[dbname]}" + encrypted_value=$(echo -n "$value" | openssl enc -aes-256-cbc -a -pbkdf2 -iter 10000 -K $key_hex -iv $iv_hex) + echo "${current[key]}NAME=$encrypted_value" >> "$encrypted_credentials_file" + + printf "Create user ${current[dbuser]}...\n" + mysql -u root -p"$mysqlrootpsw" -e "CREATE USER '${current[dbuser]}'@'localhost' IDENTIFIED BY '${current[dbpass]}';" + + # Encrypt database user + value="${current[dbuser]}" + encrypted_value=$(echo -n "$value" | openssl enc -aes-256-cbc -a -pbkdf2 -iter 10000 -K $key_hex -iv $iv_hex) + echo "${current[key]}USER=$encrypted_value" >> "$encrypted_credentials_file" + + printf "Grant ${current[dbuser]} all privileges on ${current[dbname]}...\n" + mysql -u root -p"$mysqlrootpsw" -e "GRANT ALL PRIVILEGES ON ${current[dbname]}.* TO '${current[dbuser]}'@'localhost';" + + # Encrypt database user + value="${current[dbpass]}" + encrypted_value=$(echo -n "$value" | openssl enc -aes-256-cbc -a -pbkdf2 -iter 10000 -K $key_hex -iv $iv_hex) + echo "${current[key]}PASSWORD=$encrypted_value" >> "$encrypted_credentials_file" +done + +printf "Credentials have been encrypted and stored in $encrypted_credentials_file\n" printf "Restart MySQL...\n" service mysql restart diff --git a/wp-secrets.dist.php b/wp-secrets.dist.php new file mode 100644 index 0000000..dfa0fe5 --- /dev/null +++ b/wp-secrets.dist.php @@ -0,0 +1,100 @@ +~`+=,.;:/?|'; + + // Map domains to environment names + $environment = [ + 'example.com' => 'production', + 'www.example.com' => 'production', + 'stg.example.com' => 'staging', + 'dev.example.com' => 'development', + 'example.test' => 'local' + ]; + + // Configuration of all environments + // - use arrays to map environments to different values + // - use strings when the value doesn't change between environments + // 'local' environment values, DB_HOST and WP_DEBUG_DISPLAY are never encrypted + $config = [ + 'DB_NAME' => [ + 'production' => '{{DB_NAME}}', + 'staging' => '{{STGDB_NAME}}', + 'development' => '{{DEVDB_NAME}}', + 'local' => 'dbname' // local environment is never encrypted + ], + 'DB_USER' => [ + 'production' => '{{DB_USER}}', + 'staging' => '{{STGDB_USER}}', + 'development' => '{{DEVDB_USER}}', + 'local' => 'dbuser' // local environment is never encrypted + ], + 'DB_PASSWORD' => [ + 'production' => '{{DB_PASSWORD}}', + 'staging' => '{{STGDB_PASSWORD}}', + 'development' => '{{DEVDB_PASSWORD}}', + 'local' => 'dbpassword' // local environment is never encrypted + ], + // DB_HOST is never encrypted + 'DB_HOST' => [ + 'production' => 'localhost', + 'staging' => 'localhost', + 'development' => 'localhost', + 'local' => 'localhost' + ], + // WordPress keys and salts + 'AUTH_KEY' => '{{AUTH_KEY_ENC}}', + 'SECURE_AUTH_KEY' => '{{SECURE_AUTH_KEY_ENC}}', + 'LOGGED_IN_KEY' => '{{LOGGED_IN_KEY_ENC}}', + 'NONCE_KEY' => '{{NONCE_KEY_ENC}}', + 'AUTH_SALT' => '{{AUTH_SALT_ENC}}', + 'SECURE_AUTH_SALT' => '{{SECURE_AUTH_SALT_ENC}}', + 'LOGGED_IN_SALT' => '{{LOGGED_IN_SALT_ENC}}', + 'NONCE_SALT' => '{{NONCE_SALT_ENC}}', + // WP_DEBUG_DISPLAY is never encrypted + 'WP_DEBUG_DISPLAY' => [ + 'production' => false, + 'staging' => false, + 'development' => true, + 'local' => true + ] + ]; + + // Determine the current environment + // - default to local + $env = 'local'; + if (array_key_exists(strtolower($_SERVER['HTTP_HOST']), $environment)) { + $env = $environment[strtolower($_SERVER['HTTP_HOST'])]; + } + + // Get encryption key + $key = base64_decode(getenv('WPCONFIG_ENCKEY')); + $iv = base64_decode(getenv('WPCONFIG_IV')); + + // Decrypt secrets (except for local environment) + $secrets = []; + foreach ($config as $var => $val) { + $secrets[$var] = is_array($val) ? $val[$env] : $val; + // Decrypt the value + if (!in_array($var, ['DB_HOST', 'WP_DEBUG_DISPLAY']) || $env !== 'local') { + $secrets[$var] = openssl_decrypt($secrets[$var], "AES-256-CBC", $key, 0, $iv); + } + // Generate the WordPress keys and salts for local environment + if ((strpos($var, '_KEY') !== false || strpos($var, '_SALT') !== false) && $env === 'local') { + // Generate a hash of the key name + $hash = hash('sha256', $key); + $hash .= hash('sha256', $hash); + + // Convert the hash to the allowed characters + $secrets[$key] = ''; + for ($i = 0; $i < 128; $i += 2) { + $hex = substr($hash, $i, 2); + $index = hexdec($hex) % 92; + $secrets[$key] .= $salt_chars[$index]; + } + } + } + + return $secrets; +})(); \ No newline at end of file diff --git a/wp-secrets.sh b/wp-secrets.sh new file mode 100644 index 0000000..18abe45 --- /dev/null +++ b/wp-secrets.sh @@ -0,0 +1,119 @@ +#!/bin/bash + +# wp-secrets.php Setup Script +# https://github.com/Lyquix/ubuntu-lamp + +# Check if script is being run by root +if [[ $EUID -ne 0 ]]; then + printf "This script must be run as root!\n" + exit 1 +fi + +DIVIDER="\n***************************************\n\n" + +# Welcome and instructions +printf $DIVIDER +printf "Lyquix wp-secrets.php Setup Script\n" +printf $DIVIDER + +# Prompt to continue +while true; do + read -p "Continue [Y/N]? " cnt1 + case $cnt1 in + [Yy]* ) break;; + [Nn]* ) exit;; + * ) printf "Please answer Y or N\n";; + esac +done + +CREDENTIALS_FILE="./encrypted_credentials.txt" +# Path to the apache2.conf file +apache_conf="/etc/apache2/apache2.conf" + +# Extract the ENCRYPTION_KEY +ENCRYPTION_KEY=$(grep -oP 'SetEnv WPCONFIG_ENCKEY \K.*' $apache_conf) + +# Extract the ENCRYPTION_IV +ENCRYPTION_IV=$(grep -oP 'SetEnv WPCONFIG_IV \K.*' $apache_conf) + +while IFS='=' read -r key value; do + case "$key" in + DB_NAME) DB_NAME="$value" ;; + DB_USER) DB_USER="$value" ;; + DB_PASSWORD) DB_PASSWORD="$value" ;; + STGDB_NAME) STGDB_NAME="$value" ;; + STGDB_USER) STGDB_USER="$value" ;; + STGDB_PASSWORD) STGDB_PASSWORD="$value" ;; + DEVDB_NAME) DEVDB_NAME="$value" ;; + DEVDB_USER) DEVDB_USER="$value" ;; + DEVDB_PASSWORD) DEVDB_PASSWORD="$value" ;; + esac +done < encrypted_credentials.txt + +SALTS=$(curl -s https://api.wordpress.org/secret-key/1.1/salt/) + +# Declare an associative array to hold the salt names +declare -A salt_names=( + [AUTH_KEY]="AUTH_KEY" + [SECURE_AUTH_KEY]="SECURE_AUTH_KEY" + [LOGGED_IN_KEY]="LOGGED_IN_KEY" + [NONCE_KEY]="NONCE_KEY" + [AUTH_SALT]="AUTH_SALT" + [SECURE_AUTH_SALT]="SECURE_AUTH_SALT" + [LOGGED_IN_SALT]="LOGGED_IN_SALT" + [NONCE_SALT]="NONCE_SALT" +) + +key_hex=$(echo -n $ENCRYPTION_KEY | base64 -d | xxd -p -c 32) +iv_hex=$(echo -n $ENCRYPTION_IV | base64 -d | xxd -p -c 16) + +# Loop through the salt names and process each one +for salt in "${!salt_names[@]}"; do + # Extract the salt value + value=$(echo "$SALTS" | grep "$salt" | awk -F"'" '{print $4}') + + # Encrypt the value + encrypted_value=$(echo $value | openssl enc -aes-256-cbc -a -pbkdf2 -iter 10000 -K $key_hex -iv $iv_hex) + + # Dynamically assign the encrypted value to the variable intended for the placeholder + declare ${salt_names[$salt]}="$encrypted_value" +done + +# Path to your template and the output file +template_file="./wp-secrets.dist.php" +output_file="./wp-secrets.php" + +# Update the placeholders associative array with the newly assigned encrypted salt values +declare -A placeholders=( + ["{{DB_NAME}}"]=$DB_NAME + ["{{DB_USER}}"]=$DB_USER + ["{{DB_PASSWORD}}"]=$DB_PASSWORD + ["{{STGDB_NAME}}"]=$STGDB_NAME + ["{{STGDB_USER}}"]=$STGDB_USER + ["{{STGDB_PASSWORD}}"]=$STGDB_PASSWORD + ["{{DEVDB_NAME}}"]=$DEVDB_NAME + ["{{DEVDB_USER}}"]=$DEVDB_USER + ["{{DEVDB_PASSWORD}}"]=$DEVDB_PASSWORD + ["{{AUTH_KEY_ENC}}"]=$AUTH_KEY + ["{{SECURE_AUTH_KEY_ENC}}"]=$SECURE_AUTH_KEY + ["{{LOGGED_IN_KEY_ENC}}"]=$LOGGED_IN_KEY + ["{{NONCE_KEY_ENC}}"]=$NONCE_KEY + ["{{AUTH_SALT_ENC}}"]=$AUTH_SALT + ["{{SECURE_AUTH_SALT_ENC}}"]=$SECURE_AUTH_SALT + ["{{LOGGED_IN_SALT_ENC}}"]=$LOGGED_IN_SALT + ["{{NONCE_SALT_ENC}}"]=$NONCE_SALT +) + +# Prepare the output file by clearing its contents or creating it if it doesn't exist +> "$output_file" + +# Read template and replace placeholders +while IFS= read -r line || [[ -n "$line" ]]; do + for placeholder in "${!placeholders[@]}"; do + line="${line//${placeholder}/${placeholders[$placeholder]}}" + done + # Append the processed line to the output file + echo "$line" >> "$output_file" +done < "$template_file" + +printf "wp-secrets.php has been generated successfully.\n"