From f97b51cd0780149620cc4c18ac27cf2117cd2773 Mon Sep 17 00:00:00 2001 From: VirtuBox Date: Fri, 19 Jul 2019 13:47:29 +0200 Subject: [PATCH] Add ProFTPd setup --- CHANGELOG.md | 4 +- README.md | 22 ++----- wo/cli/plugins/stack.py | 79 ++++++++++++++++++++-- wo/cli/plugins/stack_services.py | 87 ++++++++++++++++++++++++- wo/cli/templates/wpcommon-php7.mustache | 16 +++++ wo/cli/templates/wpcommon.mustache | 20 +++++- 6 files changed, 200 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 533e712..39a9a87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - Wildcard SSL Certificates support with DNS validation - Acme challenge validation with DNS API (Cloudflare, DigitalOcean, etc ..) on domain, subdomain, and wildcard - Flag `--letsencrypt=clean` to purge a previous SSL configuration -- Support for Debian 10 (buster) in beta +- Support for Debian 10 buster (testing - not ready for production) +- Fail2ban with custom jails to secure WordPress & SSH +- Variable `keylength` in /etc/wo/wo.conf to define letsencrypt certificate keylenght #### Fixed diff --git a/README.md b/README.md index a15cbc0..11433b9 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ - Ubuntu 19.04 (Disco) - Debian 8 (Jessie) - Debian 9 (Stretch) -- Debian 10 (Buster) +- Debian 10 (Buster) - Not ready for production - Raspbian 9 (Stretch) ### Ports requirements @@ -78,20 +78,7 @@ sudo wo site create example.com --wp # Install required packages & setup Wor ## Must read -WordOps made some fundamental changes: - -- We've deprecated the mail stack. As an alternative, you can take a look at [Mail-in-a-Box](https://github.com/mail-in-a-box/mailinabox), [iRedMail](https://www.iredmail.org/) or [Caesonia](https://github.com/vedetta-com/caesonia). As Roundcube alternative, there is [Rainloop](https://www.rainloop.net/) or [Afterlogic WebMail](https://github.com/afterlogic/webmail-lite-8) -- Support for w3tc is dropped as a security precaution. -- PHP 5.6 has been replaced by PHP 7.2 and PHP 7.0 has been replaced by PHP 7.3. -- Nginx-ee package has been replaced by Nginx-wo (based on Nginx stable v1.16.0 with Brotli support) -- HHVM stack has been removed -- Let's Encrypt stack isn't based on letsencrypt-auto anymore, we use acme.sh to handle SSL certificates - -If you are going to migrate from EasyEngine v3, here some important informations : - -- Previous php upstreams in Nginx will not be overwritted -- php5.6 and php7.0 will not be removed or uninstalled -- previous Nginx common configurations will not be overwritted +[From EasyEngine to WordOps](https://docs.wordops.net/about/from-easyengine-to-wordops/) ## Usage @@ -135,9 +122,10 @@ wo site create example.com --proxy=127.0.0.1:3000 # create example.com with ngi ### Sites secured with Let's Encrypt ```bash -wo site create example.com --wp --letsencrypt # install wordpress & secure site with letsencrypt -wo site create sub.example.com --wp --letsencrypt=subdomain # install wordpress and secure subdomain with letsencrypt +wo site create example.com --wp --letsencrypt # wordpress secured with letsencrypt +wo site create sub.example.com --wp --letsencrypt=subdomain # wordpress + letsencrypt subdomain wo site create site.tld --wp --letsencrypt --hsts # install wordpress & secure site with letsencrypt with HSTS +wo site create site.tld --wp --letsencrypt=wildcard --dns=dns_cf # install wordpress & issue a wildcard SSL certificate with Cloudflare DNS API ``` ## Update WordOps diff --git a/wo/cli/plugins/stack.py b/wo/cli/plugins/stack.py index e4ab4b7..9fcd480 100644 --- a/wo/cli/plugins/stack.py +++ b/wo/cli/plugins/stack.py @@ -79,6 +79,8 @@ class WOStackController(CementBaseController): dict(help='Install Redis', action='store_true')), (['--phpredisadmin'], dict(help='Install phpRedisAdmin', action='store_true')), + (['--proftpd'], + dict(help='Install ProFTPd', action='store_true')), ] usage = "wo stack (command) [options]" @@ -1103,6 +1105,37 @@ class WOStackController(CementBaseController): msg="Adding Fail2ban into Git") WOService.reload_service(self, 'fail2ban') + if set(["proftpd-basic"]).issubset(set(apt_packages)): + if os.path.isfile("/etc/proftpd/proftpd.conf"): + Log.debug(self, "Setting up Proftpd configuration") + WOFileUtils.searchreplace(self, "/etc/proftpd/" + "proftpd.conf", + "# DefaultRoot", + "DefaultRoot") + WOFileUtils.searchreplace(self, "/etc/proftpd/" + "proftpd.conf", + "# RequireValidShell", + "RequireValidShell") + WOFileUtils.searchreplace(self, "/etc/proftpd/" + "proftpd.conf", + "# PassivePorts " + " " + "49152 65534", + "PassivePorts " + " " + " 49000 50000") + + if WOAptGet.is_installed(self, 'ufw'): + try: + WOShellExec.cmd_exec(self, "ufw allow " + "49000:50000/tcp") + except CommandExecutionError as e: + Log.error(self, "Unable to add UFW rules") + + WOGit.add(self, ["/etc/proftpd"], + msg="Adding ProFTPd into Git") + WOService.reload_service(self, 'proftpd') + if (packages): if any('/usr/local/bin/wp' == x[1] for x in packages): Log.debug(self, "Setting Privileges" @@ -1421,7 +1454,7 @@ class WOStackController(CementBaseController): (not self.app.pargs.dashboard) and (not self.app.pargs.fail2ban) and (not self.app.pargs.adminer) and (not self.app.pargs.utils) and - (not self.app.pargs.redis) and + (not self.app.pargs.redis) and (not self.app.pargs.proftpd) and (not self.app.pargs.phpredisadmin) and (not self.app.pargs.php73)): self.app.pargs.web = True @@ -1432,6 +1465,7 @@ class WOStackController(CementBaseController): self.app.pargs.admin = True self.app.pargs.php73 = True self.app.pargs.redis = True + self.app.pargs.proftpd = True if self.app.pargs.web: self.app.pargs.nginx = True @@ -1547,6 +1581,15 @@ class WOStackController(CementBaseController): Log.debug(self, "Fail2ban already installed") Log.info(self, "Fail2ban already installed") + # proftpd + if self.app.pargs.proftpd: + Log.debug(self, "Setting apt_packages variable for ProFTPd") + if not WOAptGet.is_installed(self, 'proftpd-basic'): + apt_packages = apt_packages + ["proftpd-basic"] + else: + Log.debug(self, "ProFTPd already installed") + Log.info(self, "ProFTPd already installed") + # PHPMYADMIN if self.app.pargs.phpmyadmin: if not os.path.isdir('/var/www/22222/htdocs/db/pma'): @@ -1741,7 +1784,7 @@ class WOStackController(CementBaseController): (not self.app.pargs.wpcli) and (not self.app.pargs.phpmyadmin) and (not self.app.pargs.adminer) and (not self.app.pargs.utils) and (not self.app.pargs.composer) and (not self.app.pargs.netdata) and - (not self.app.pargs.fail2ban) and + (not self.app.pargs.fail2ban) and (not self.app.pargs.proftpd) and (not self.app.pargs.all) and (not self.app.pargs.redis) and (not self.app.pargs.phpredisadmin)): self.app.pargs.web = True @@ -1813,8 +1856,19 @@ class WOStackController(CementBaseController): # fail2ban if self.app.pargs.fail2ban: - Log.debug(self, "Remove apt_packages variable of Fail2ban") - apt_packages = apt_packages + WOVariables.wo_fail2ban + if WOAptGet.is_installed(self, 'fail2ban'): + Log.debug(self, "Remove apt_packages variable of Fail2ban") + apt_packages = apt_packages + WOVariables.wo_fail2ban + else: + Log.error(self, "Fail2ban not found") + + # proftpd + if self.app.pargs.proftpd: + if WOAptGet.is_installed(self, 'proftpd-basic'): + Log.debug(self, "Remove apt_packages variable for ProFTPd") + apt_packages = apt_packages + ["proftpd-basic"] + else: + Log.error(self, "ProFTPd not found") # WPCLI if self.app.pargs.wpcli: @@ -1920,7 +1974,7 @@ class WOStackController(CementBaseController): (not self.app.pargs.wpcli) and (not self.app.pargs.phpmyadmin) and (not self.app.pargs.adminer) and (not self.app.pargs.utils) and (not self.app.pargs.composer) and (not self.app.pargs.netdata) and - (not self.app.pargs.fail2ban) and + (not self.app.pargs.fail2ban) and (not self.app.pargs.proftpd) (not self.app.pargs.all) and (not self.app.pargs.redis) and (not self.app.pargs.phpredisadmin)): self.app.pargs.web = True @@ -1981,8 +2035,19 @@ class WOStackController(CementBaseController): # fail2ban if self.app.pargs.fail2ban: - Log.debug(self, "Remove apt_packages variable of Fail2ban") - apt_packages = apt_packages + WOVariables.wo_fail2ban + if WOAptGet.is_installed(self, 'fail2ban'): + Log.debug(self, "Purge apt_packages variable of Fail2ban") + apt_packages = apt_packages + WOVariables.wo_fail2ban + else: + Log.error(self, "Fail2ban not found") + + # proftpd + if self.app.pargs.proftpd: + if WOAptGet.is_installed(self, 'proftpd-basic'): + Log.debug(self, "Purge apt_packages variable for ProFTPd") + apt_packages = apt_packages + ["proftpd-basic"] + else: + Log.error(self, "ProFTPd not found") # WP-CLI if self.app.pargs.wpcli: diff --git a/wo/cli/plugins/stack_services.py b/wo/cli/plugins/stack_services.py index 2def4af..56985b8 100644 --- a/wo/cli/plugins/stack_services.py +++ b/wo/cli/plugins/stack_services.py @@ -4,6 +4,7 @@ from wo.core.services import WOService from wo.core.logging import Log from wo.core.variables import WOVariables from wo.core.aptget import WOAptGet +import os class WOStackStatusController(CementBaseController): @@ -22,6 +23,7 @@ class WOStackStatusController(CementBaseController): self.app.pargs.mysql or self.app.pargs.redis or self.app.pargs.fail2ban or + self.app.pargs.proftpd or self.app.pargs.netdata): self.app.pargs.nginx = True self.app.pargs.php = True @@ -74,6 +76,20 @@ class WOStackStatusController(CementBaseController): else: Log.info(self, "fail2ban is not installed") + # proftpd + if self.app.pargs.proftpd: + if WOAptGet.is_installed(self, 'proftpd-basic'): + services = services + ['proftpd'] + else: + Log.info(self, "ProFTPd is not installed") + + # netdata + if self.app.pargs.netdata: + if os.path.isdir("/opt/netdata"): + services = services + ['netdata'] + else: + Log.info(self, "Netdata is not installed") + for service in services: Log.debug(self, "Starting service: {0}".format(service)) WOService.start_service(self, service) @@ -86,17 +102,21 @@ class WOStackStatusController(CementBaseController): self.app.pargs.php73 or self.app.pargs.mysql or self.app.pargs.fail2ban or + self.app.pargs.netdata or + self.app.pargs.proftpd or self.app.pargs.redis): self.app.pargs.nginx = True self.app.pargs.php = True self.app.pargs.mysql = True + # nginx if self.app.pargs.nginx: if (WOAptGet.is_installed(self, 'nginx-custom')): services = services + ['nginx'] else: Log.info(self, "Nginx is not installed") + # php7.2 if self.app.pargs.php: if WOAptGet.is_installed(self, 'php7.2-fpm'): services = services + ['php7.2-fpm'] @@ -108,12 +128,14 @@ class WOStackStatusController(CementBaseController): else: Log.info(self, "PHP7.3-FPM is not installed") + # php7.3 if self.app.pargs.php73: if WOAptGet.is_installed(self, 'php7.3-fpm'): services = services + ['php7.3-fpm'] else: Log.info(self, "PHP7.3-FPM is not installed") + # mysql if self.app.pargs.mysql: if ((WOVariables.wo_mysql_host is "localhost") or (WOVariables.wo_mysql_host is "127.0.0.1")): @@ -127,18 +149,34 @@ class WOStackStatusController(CementBaseController): Log.warn(self, "Remote MySQL found, " "Unable to check MySQL service status") + # redis if self.app.pargs.redis: if WOAptGet.is_installed(self, 'redis-server'): services = services + ['redis-server'] else: Log.info(self, "Redis server is not installed") + # fail2ban if self.app.pargs.fail2ban: if WOAptGet.is_installed(self, 'fail2ban'): services = services + ['fail2ban'] else: Log.info(self, "fail2ban is not installed") + # proftpd + if self.app.pargs.proftpd: + if WOAptGet.is_installed(self, 'proftpd-basic'): + services = services + ['proftpd'] + else: + Log.info(self, "ProFTPd is not installed") + + # netdata + if self.app.pargs.netdata: + if os.path.isdir("/opt/netdata"): + services = services + ['netdata'] + else: + Log.info(self, "Netdata is not installed") + for service in services: Log.debug(self, "Stopping service: {0}".format(service)) WOService.stop_service(self, service) @@ -150,7 +188,8 @@ class WOStackStatusController(CementBaseController): if not (self.app.pargs.nginx or self.app.pargs.php or self.app.pargs.php73 or self.app.pargs.mysql or - self.app.pargs.memcached or + self.app.pargs.netdata or + self.app.pargs.proftpd or self.app.pargs.redis or self.app.pargs.fail2ban): self.app.pargs.nginx = True @@ -206,6 +245,20 @@ class WOStackStatusController(CementBaseController): else: Log.info(self, "fail2ban is not installed") + # proftpd + if self.app.pargs.proftpd: + if WOAptGet.is_installed(self, 'proftpd-basic'): + services = services + ['proftpd'] + else: + Log.info(self, "ProFTPd is not installed") + + # netdata + if self.app.pargs.netdata: + if os.path.isdir("/opt/netdata"): + services = services + ['netdata'] + else: + Log.info(self, "Netdata is not installed") + for service in services: Log.debug(self, "Restarting service: {0}".format(service)) WOService.restart_service(self, service) @@ -217,6 +270,8 @@ class WOStackStatusController(CementBaseController): if not (self.app.pargs.nginx or self.app.pargs.php or self.app.pargs.php73 or self.app.pargs.mysql or + self.app.pargs.netdata or + self.app.pargs.proftpd or self.app.pargs.redis or self.app.pargs.fail2ban): self.app.pargs.nginx = True @@ -271,6 +326,20 @@ class WOStackStatusController(CementBaseController): else: Log.info(self, "fail2ban is not installed") + # proftpd + if self.app.pargs.proftpd: + if WOAptGet.is_installed(self, 'proftpd-basic'): + services = services + ['proftpd'] + else: + Log.info(self, "ProFTPd is not installed") + + # netdata + if self.app.pargs.netdata: + if os.path.isdir("/opt/netdata"): + services = services + ['netdata'] + else: + Log.info(self, "Netdata is not installed") + for service in services: if WOService.get_service_status(self, service): Log.info(self, "{0:10}: {1}".format(service, "Running")) @@ -282,6 +351,8 @@ class WOStackStatusController(CementBaseController): if not (self.app.pargs.nginx or self.app.pargs.php or self.app.pargs.php73 or self.app.pargs.mysql or + self.app.pargs.netdata or + self.app.pargs.proftpd or self.app.pargs.redis or self.app.pargs.fail2ban): self.app.pargs.nginx = True @@ -337,6 +408,20 @@ class WOStackStatusController(CementBaseController): else: Log.info(self, "fail2ban is not installed") + # proftpd + if self.app.pargs.proftpd: + if WOAptGet.is_installed(self, 'proftpd-basic'): + services = services + ['proftpd'] + else: + Log.info(self, "ProFTPd is not installed") + + # netdata + if self.app.pargs.netdata: + if os.path.isdir("/opt/netdata"): + services = services + ['netdata'] + else: + Log.info(self, "Netdata is not installed") + for service in services: Log.debug(self, "Reloading service: {0}".format(service)) WOService.reload_service(self, service) diff --git a/wo/cli/templates/wpcommon-php7.mustache b/wo/cli/templates/wpcommon-php7.mustache index d0ee50d..85d2739 100644 --- a/wo/cli/templates/wpcommon-php7.mustache +++ b/wo/cli/templates/wpcommon-php7.mustache @@ -40,6 +40,22 @@ location /wp-content/uploads { deny all; } } +# webp rewrite rules for EWWW testing image +location /wp-content/plugins/ewww-image-optimizer/images { + location ~ \.(png|jpe?g)$ { + add_header Vary "Accept-Encoding"; + add_header "Access-Control-Allow-Origin" "*"; + add_header Cache-Control "public, no-transform"; + access_log off; + log_not_found off; + expires max; + try_files $uri$webp_suffix $uri =404; + } + location ~ \.php$ { +#Prevent Direct Access Of PHP Files From Web Browsers + deny all; + } +} # Deny access to any files with a .php extension in the uploads directory # Works in sub-directory installs and also in multisite network # Keep logging the requests to parse later (or to pass to firewall utilities such as fail2ban) diff --git a/wo/cli/templates/wpcommon.mustache b/wo/cli/templates/wpcommon.mustache index 581710c..7e6edb3 100644 --- a/wo/cli/templates/wpcommon.mustache +++ b/wo/cli/templates/wpcommon.mustache @@ -21,7 +21,7 @@ location = /robots.txt { } # fallback for robots.txt with default wordpress rules location @robots { - return 200 "User-agent: *\nDisallow: /wp-admin/\nAllow: /wp-admin/admin-ajax.php\n"; + return 200 "User-agent: *\nDisallow: /wp-admin/\nAllow: /wp-admin/admin-ajax.php\n"; } # webp rewrite rules for jpg and png images # try to load alternative image.png.webp before image.png @@ -36,7 +36,23 @@ location /wp-content/uploads { try_files $uri$webp_suffix $uri =404; } location ~ \.php$ { - #Prevent Direct Access Of PHP Files From Web Browsers +#Prevent Direct Access Of PHP Files From Web Browsers + deny all; + } +} +# webp rewrite rules for EWWW testing image +location /wp-content/plugins/ewww-image-optimizer/images { + location ~ \.(png|jpe?g)$ { + add_header Vary "Accept-Encoding"; + add_header "Access-Control-Allow-Origin" "*"; + add_header Cache-Control "public, no-transform"; + access_log off; + log_not_found off; + expires max; + try_files $uri$webp_suffix $uri =404; + } + location ~ \.php$ { +#Prevent Direct Access Of PHP Files From Web Browsers deny all; } }