Skip to content

Instantly share code, notes, and snippets.

@JamesTheHacker
Created December 9, 2019 19:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JamesTheHacker/10d07d8fa473fc1c83423253f5deb2dd to your computer and use it in GitHub Desktop.
Save JamesTheHacker/10d07d8fa473fc1c83423253f5deb2dd to your computer and use it in GitHub Desktop.
WIP provision script for magento
#!/usr/bin/env bash
# Developer: jamie@jr2.co.uk
set -o errexit
set -o pipefail
set -o nounset
export DEBIAN_FRONTEND=noninteractive
mode="developer"
domain="ucp.local"
protocol="http"
# Public key that will be added to authorized_keys for SSH login by the unprivilaged user
public_key=""
# Github personal access token
github_pa_token=""
# Megento
magento_public_key=""
magento_private_key=""
magento_language="en_GB"
magento_currency="GBP"
magento_timezone="Europe/London"
magento_backend="admin_ucp_654678"
# Database
db_username="magento"
db_password=""
db_root_password=""
db_name="magento_shop"
db_port="3306"
db_host="localhost:${db_port}"
# Webmaster
webmaster_email=""
webmaster_username="jamie"
webmaster_password=""
# Unprivileged user's username
user="web"
# Unprivileged user's home
home="/home/${user}"
# Webroot directory
webroot="/var/www/shop"
# PHP version
phpv="7.2"
# php.ini location
phpini="/etc/php/${phpv}/fpm/php.ini"
# php.ini settings
php_mem_limit="2G"
php_date_timezone="Europe\/London" # \ required to escape for sed
##########################################################################################
# WARNING: DO NOT MESS WITH ANYTHING BEYOND THIS POINT UNLESS YOU KNOW WHAT YOU'RE DOING #
##########################################################################################
apt -y update
apt -y upgrade
apt install -y \
curl \
nginx \
unzip \
git \
openssl \
libxml2 \
mysql-server \
mysql-client \
"php${phpv}-fpm" \
php-dev \
php-bcmath \
php-ctype \
php-curl \
php-dom \
php-gd \
php-iconv \
php-intl \
php-mbstring \
php-mysql \
php-simplexml \
php-soap \
php-xsl \
php-zip \
wget \
software-properties-common
# DEV ONLY: For some reason the box has it installed by default :S
apt remove -y apache2
# Configure/Secure MySQL
mysql -u root <<ILOVEBASH
UPDATE mysql.user SET authentication_string=PASSWORD('${db_root_password}') WHERE User='root';
DELETE FROM mysql.user WHERE User='';
DELETE FROM mysql.user WHERE User='root' AND Host NOT IN ('localhost', '127.0.0.1', '::1');
DROP DATABASE IF EXISTS test;
DELETE FROM mysql.db WHERE Db='test' OR Db='test\\_%';
CREATE DATABASE ${db_name};
CREATE USER '${db_username}'@'localhost' IDENTIFIED WITH mysql_native_password BY '${db_password}';
GRANT ALL ON ${db_name}.* TO ${db_username}@localhost;
FLUSH PRIVILEGES;
ILOVEBASH
# Install composer globally
curl -sS https://getcomposer.org/installer -o /tmp/composer-setup.php
php /tmp/composer-setup.php --install-dir=/usr/local/bin --filename=composer
#########################################################################
# Secure PHP7.x #
#########################################################################
# Compile snuffleupagus for PHP7 hardening
#git clone https://github.com/nbs-system/snuffleupagus /tmp/snuffleupagus
#cd /tmp/snuffleupagus/src
#phpize
#./configure --enable-snuffleupagus
#make
#make install
#make test
#rm -rf /tmp/snuffleupagus
# Add snuffleupagus rules
tee /etc/php/"${phpv}"/fpm/conf.d/snuffleupagus.rules > /dev/null <<'ILOVEBASH'
# Harden the PRNG
sp.harden_random.enable();
# Disabled XXE
sp.disable_xxe.enable();
# Globally activate strict mode
sp.global_strict.enable();
# Prevent unserialize-related exploits
sp.unserialize_hmac.enable();
# Only allow execution of read-only files. This is a low-hanging fruit that you should enable.
sp.readonly_exec.enable();
# Php has a lot of wrappers, most of them aren't usually useful, you should only enable the ones you're using.
sp.wrappers_whitelist.list("file,php,phar");
# Prevent sloppy comparisons.
sp.sloppy_comparison.enable();
# use SameSite on session cookie
# https://snuffleupagus.readthedocs.io/features.html#protection-against-cross-site-request-forgery
sp.cookie.name("PHPSESSID").samesite("lax");
# Harden the `chmod` function
sp.disable_function.function("chmod").param("mode").value_r("^[0-9]{2}[67]$").drop();
# Prevent various `mail`-related vulnerabilities
sp.disable_function.function("mail").param("additional_parameters").value_r("\\-").drop();
# Since it's now burned, me might as well mitigate it publicly
sp.disable_function.function("putenv").param("setting").value_r("LD_").drop()
# This is also burned:
# ini_set('open_basedir','..');chdir('..');…;chdir('..');ini_set('open_basedir','/');echo(file_get_contents('/etc/passwd'));
# Since we have no way of matching on two parameters at the same time, we're
# blocking calls to open_basedir altogether: nobody is using it via ini_set anyway.
# Moreover, there are non-public bypasses that are also using this vector ;)
sp.disable_function.function("ini_set").param("varname").value_r("open_basedir").drop()
# Prevent various `include`-related vulnerabilities
sp.disable_function.function("require_once").value_r("\.(inc|phtml|php)$").allow();
sp.disable_function.function("include_once").value_r("\.(inc|phtml|php)$").allow();
sp.disable_function.function("require").value_r("\.(inc|phtml|php)$").allow();
sp.disable_function.function("include").value_r("\.(inc|phtml|php)$").allow();
sp.disable_function.function("require_once").drop()
sp.disable_function.function("include_once").drop()
sp.disable_function.function("require").drop()
sp.disable_function.function("include").drop()
# Prevent `system`-related injections
sp.disable_function.function("system").param("command").value_r("[$|;&`\\n\\(\\)\\\\]").drop();
sp.disable_function.function("shell_exec").param("command").value_r("[$|;&`\\n\\(\\)\\\\]").drop();
sp.disable_function.function("exec").param("command").value_r("[$|;&`\\n\\(\\)\\\\]").drop();
sp.disable_function.function("proc_open").param("command").value_r("[$|;&`\\n\\(\\)\\\\]").drop();
# Prevent runtime modification of interesting things
sp.disable_function.function("ini_set").param("varname").value("assert.active").drop();
sp.disable_function.function("ini_set").param("varname").value("zend.assertions").drop();
sp.disable_function.function("ini_set").param("varname").value("memory_limit").drop();
sp.disable_function.function("ini_set").param("varname").value("include_path").drop();
sp.disable_function.function("ini_set").param("varname").value("open_basedir").drop();
# Detect some backdoors via environnement recon
sp.disable_function.function("ini_get").param("varname").value("allow_url_fopen").drop();
sp.disable_function.function("ini_get").param("varname").value("open_basedir").drop();
sp.disable_function.function("ini_get").param("varname").value_r("suhosin").drop();
sp.disable_function.function("function_exists").param("function_name").value("eval").drop();
sp.disable_function.function("function_exists").param("function_name").value("exec").drop();
sp.disable_function.function("function_exists").param("function_name").value("system").drop();
sp.disable_function.function("function_exists").param("function_name").value("shell_exec").drop();
sp.disable_function.function("function_exists").param("function_name").value("proc_open").drop();
sp.disable_function.function("function_exists").param("function_name").value("passthru").drop();
sp.disable_function.function("is_callable").param("var").value("eval").drop();
sp.disable_function.function("is_callable").param("var").value("exec").drop();
sp.disable_function.function("is_callable").param("var").value("system").drop();
sp.disable_function.function("is_callable").param("var").value("shell_exec").drop();
sp.disable_function.function("is_callable").param("var").value("proc_open").drop();
sp.disable_function.function("is_callable").param("var").value("passthru").drop();
# Ensure that certificates are properly verified
sp.disable_function.function("curl_setopt").param("value").value("1").allow();
sp.disable_function.function("curl_setopt").param("value").value("2").allow();
# `81` is SSL_VERIFYHOST and `64` SSL_VERIFYPEER
sp.disable_function.function("curl_setopt").param("option").value("64").drop().alias("Please don't turn CURLOPT_SSL_VERIFYCLIENT off.");
sp.disable_function.function("curl_setopt").param("option").value("81").drop().alias("Please don't turn CURLOPT_SSL_VERIFYHOST off.");
#File upload
sp.disable_function.function("move_uploaded_file").param("destination").value_r("\\.ph").drop();
sp.disable_function.function("move_uploaded_file").param("destination").value_r("\\.ht").drop();
ILOVEBASH
# TODO: Fix the rules for Magento 2
# Add snuffleupagus extension to PHP config
sudo tee /etc/php/"${phpv}"/fpm/conf.d/20-snuffleupagus.ini > /dev/null <<ILOVEBASH
;extension=snuffleupagus.so
;sp.configuration_file=/etc/php/${phpv}/fpm/conf.d/snuffleupagus.rules
ILOVEBASH
# Create a new user, add to www-data group
useradd -m "${user}" -G www-data
# SSH doesn't allow password-less users by default and will report
# the account is locked. As I use pubkey auth I am ok with that.
usermod -p "*" "${user}"
# Secure SSH
tee /etc/ssh/sshd_config > /dev/null <<ILOVEBASH
X11Forwarding no
PubkeyAuthentication yes
PasswordAuthentication no
PermitRootLogin no
AllowUsers ${user}
Port 22
AuthorizedKeysFile .ssh/authorized_keys
ILOVEBASH
# Add public key for unprivilaged user
sudo -u "${user}" bash -s <<ILOVEBASH
mkdir $home/.ssh
printf "${public_key}" | tee --append ${home}/.ssh/authorized_keys
chmod 700 ${home}/.ssh
chmod 600 ${home}/.ssh/authorized_keys
ILOVEBASH
# Modify PHP configuration
sed -r -i 's/^memory_limit ?=.*/memory_limit = '${php_mem_limit}'/' /etc/php/7.2/fpm/php.ini
sed -r -i 's/^;date.timezone ?=.*$/date.timezone = "'${php_date_timezone}'"/' /etc/php/7.2/fpm/php.ini
sed -r -i 's/^;opcache.save_comments ?=.*$/opcache.save_comments = 1/' /etc/php/7.2/fpm/php.ini
sed -r -i 's/^max_file_uploads ?=.*$/max_file_uploads = 5/' /etc/php/7.2/fpm/php.ini
# Configure nginx for magento
tee /etc/nginx/sites-available/default > /dev/null <<ILOVEBASH
upstream fastcgi_backend {
server unix:/var/run/php/php${phpv}-fpm.sock;
}
server {
listen 80;
server_name ${domain};
set \$MAGE_ROOT ${webroot};
set \$MAGE_DEBUG_SHOW_ARGS 1;
include ${webroot}/nginx.conf.sample;
}
ILOVEBASH
# Permissions for php-fpm and nginx logs
chown :www-data "/var/log/php${phpv}-fpm.log"
chmod 640 "/var/log/php${phpv}-fpm.log"
chown :www-data /var/log/nginx/access.log
chown :www-data /var/log/nginx/error.log
###############################
# Magento #
###############################
rm -rf "${webroot}"
mkdir "${webroot}"
# Change permissions for webroot directory
chown "${user}":www-data "${webroot}"
# Create auth.json required for magento installation
sudo -u "${user}" bash -c "mkdir ${home}/.composer"
sudo -u "${user}" bash -c "tee ${home}/.composer/auth.json" > /dev/null <<ILOVEBASH
{
"github-oauth": {
"github.com": "${github_pa_token}"
},
"http-basic": {
"repo.magento.com": {
"username": "${magento_public_key}",
"password": "${magento_private_key}"
}
}
}
ILOVEBASH
cd "${webroot}"
# Setup magento
sudo -H -u "${user}" bash -c "composer create-project --repository=https://repo.magento.com/ magento/project-community-edition ."
# Magento permissions
find var generated vendor pub/static pub/media app/etc -type f -exec chmod g+w {} +
find var generated vendor pub/static pub/media app/etc -type d -exec chmod g+ws {} +
chown -R :www-data .
chmod u+x bin/magento
# Install Magento
sudo -u "${user}" bash -s <<ILOVEBASH
bin/magento setup:install \
--base-url="${protocol}://${domain}" \
--db-host="${db_host}" \
--db-name="${db_name}" \
--db-user="${db_username}" \
--db-password="${db_password}" \
--admin-firstname=admin \
--admin-lastname=admin \
--admin-email="${webmaster_email}" \
--admin-user="${webmaster_username}" \
--admin-password="${webmaster_password}" \
--language="${magento_language}" \
--currency="${magento_currency}" \
--timezone="${magento_timezone}" \
--use-rewrites=1 \
--cleanup-database \
--backend-frontname "${magento_backend}"
bin/magento deploy:mode:set ${mode}
ln -s ${home}/.composer/auth.json ${webroot}/auth.json
bin/magento cron:install
bin/magento setup:upgrade
ILOVEBASH
# If developer mode add some sample data
if [[ "${mode}" == "developer" ]]; then
sudo -u "${user}" bash -s <<ILOVEBASH
bin/magento cache:disable
bin/magento sampledata:deploy
ln -s /vagrant/jr2 ${webroot}/app/design/frontend/jr2
bin/magento setup:upgrade
ILOVEBASH
fi
# Create SSL certificate for domain
if [[ "${mode}" != "developer" ]]; then
echo "Setting up SSL certificate"
wget https://dl.eff.org/certbot-auto
mv certbot-auto /usr/local/bin/certbot-auto
chown root /usr/local/bin/certbot-auto
chmod 0755 /usr/local/bin/certbot-autos
/usr/local/bin/certbot-auto --non-interactive --agree-tos --email ${webmaster_email} --nginx --domains ${domain}
fi
# Reload everything
nginx -t
systemctl reload nginx
/etc/init.d/mysql restart
systemctl reload "php${phpv}-fpm"
systemctl reload sshd
# Configure firewall
ufw allow ssh
ufw allow http
ufw allow https
ufw enable
echo "Magento server successfully configured :)"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment