diff --git a/CHANGELOG.md b/CHANGELOG.md index b222577..4491648 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,15 +8,39 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### v3.9.x - [Unreleased] +### v3.9.9.1 - 2019-09-26 + +#### Added + +- [SECURE] Allow new ssh port with UFW when running `wo secure --sshport` +- [STACK] Additional Nginx directives to prevent access to log files or backup from web browser +- [CORE] apt-mirror-updater to select the fastest debian/ubuntu mirror with automatic switching between mirrors if the current mirror is being updated +- [SITE] add `--force` to force Let's Encrypt certificate issuance even if DNS check fail +- [STACK] check if another mta is installed before installing sendmail +- [SECURE] `--allowpassword` to allow password when using `--ssh` with `wo secure` + +#### Changed + +- [SECURE] Improved sshd_config template according to Mozilla Infosec guidelines +- [STACK] Always add stack configuration into Git before making changes to make rollback easier +- [STACK] Render php-fpm pools configuration from template +- [STACK] Adminer updated to v4.7.3 + +#### Fixed + +- [STACK] UFW setup after removing all stacks with `wo stack purge --all` +- [CONFIG] Invalid CORS header +- [STACK] PHP-FPM stack upgrade failure due to pool configuration + ### v3.9.9 - 2019-09-24 #### Added - [STACK] UFW now available as a stack with flag `--ufw` -- [SECURE] `wo stack secure --ssh` to harden ssh security -- [SECURE] `wo stack secure --sshport` to change ssh port +- [SECURE] `wo secure --ssh` to harden ssh security +- [SECURE] `wo secure --sshport` to change ssh port - [SITE] check domain DNS records before issuing a new certificate without DNS API -- [STACK] Acme challenge with DNS Alias mode [acme.sh wiki](https://github.com/Neilpang/acme.sh/wiki/DNS-alias-mode) +- [STACK] Acme challenge with DNS Alias mode `--dnsalias=aliasdomain.tld` [acme.sh wiki](https://github.com/Neilpang/acme.sh/wiki/DNS-alias-mode) #### Changed diff --git a/setup.py b/setup.py index cf655c2..0bf7c71 100644 --- a/setup.py +++ b/setup.py @@ -25,7 +25,7 @@ if not os.path.exists('/var/lib/wo/'): os.makedirs('/var/lib/wo/') setup(name='wo', - version='3.9.9', + version='3.9.9.1', description=long_description, long_description=long_description, classifiers=[], @@ -56,6 +56,7 @@ setup(name='wo', 'SQLAlchemy', 'requests', 'distro', + 'apt-mirror-updater', ], data_files=[('/etc/wo', ['config/wo.conf']), ('/etc/wo/plugins.d', conf), diff --git a/wo/cli/plugins/secure.py b/wo/cli/plugins/secure.py index 89e4012..0c8a968 100644 --- a/wo/cli/plugins/secure.py +++ b/wo/cli/plugins/secure.py @@ -4,6 +4,7 @@ import os from cement.core import handler, hook from cement.core.controller import CementBaseController, expose +from wo.core.fileutils import WOFileUtils from wo.core.git import WOGit from wo.core.logging import Log from wo.core.random import RANDOM @@ -37,6 +38,9 @@ class WOSecureController(CementBaseController): help='set custom ssh port', action='store_true')), (['--ssh'], dict( help='harden ssh security', action='store_true')), + (['--allowpassword'], dict( + help='allow password authentification ' + 'when hardening ssh security', action='store_true')), (['--force'], dict(help='force execution without being prompt', action='store_true')), @@ -156,7 +160,7 @@ class WOSecureController(CementBaseController): def secure_ssh(self): """Harden ssh security""" pargs = self.app.pargs - if not pargs.force: + if not pargs.force and not pargs.allowpassword: start_secure = input('Are you sure you to want to' ' harden SSH security ?' '\nSSH login with password will not ' @@ -165,6 +169,8 @@ class WOSecureController(CementBaseController): 'Harden SSH security [y/N]') if start_secure != "Y" and start_secure != "y": Log.error(self, "Not hardening SSH security") + WOGit.add(self, ["/etc/ssh"], + msg="Adding SSH into Git") Log.debug(self, "check if /etc/ssh/sshd_config exist") if os.path.isfile('/etc/ssh/sshd_config'): Log.debug(self, "looking for the current ssh port") @@ -178,7 +184,11 @@ class WOSecureController(CementBaseController): sudo_user = os.getenv('SUDO_USER') else: sudo_user = '' - data = dict(sshport=current_ssh_port, allowpass='no', + if pargs.allowpassword: + wo_allowpassword = 'yes' + else: + wo_allowpassword = 'no' + data = dict(sshport=current_ssh_port, allowpass=wo_allowpassword, user=sudo_user) WOTemplate.deploy(self, '/etc/ssh/sshd_config', 'sshd.mustache', data) @@ -213,8 +223,23 @@ class WOSecureController(CementBaseController): WOShellExec.cmd_exec(self, "sed -i \"s/Port.*/Port " "{port}/\" /etc/ssh/sshd_config" .format(port=pargs.user_input)) + # allow new ssh port if ufw is enabled + if os.path.isfile('/etc/ufw/ufw.conf'): + # add rule for proftpd with UFW + if WOFileUtils.grepcheck( + self, '/etc/ufw/ufw.conf', 'ENABLED=yes'): + try: + WOShellExec.cmd_exec( + self, 'ufw limit {0}'.format(pargs.user_input)) + WOShellExec.cmd_exec( + self, 'ufw reload') + except Exception as e: + Log.debug(self, "{0}".format(e)) + Log.error(self, "Unable to add UFW rule") + # add ssh into git WOGit.add(self, ["/etc/ssh"], msg="Adding changed SSH port into Git") + # restart ssh service if not WOService.restart_service(self, 'ssh'): Log.error(self, "service SSH restart failed.") Log.info(self, "Successfully changed SSH port to {port}" diff --git a/wo/cli/plugins/site.py b/wo/cli/plugins/site.py index cf6176a..5c1fe78 100644 --- a/wo/cli/plugins/site.py +++ b/wo/cli/plugins/site.py @@ -368,6 +368,9 @@ class WOSiteCreateController(CementBaseController): action='store' or 'store_const', choices=('on', 'subdomain', 'wildcard'), const='on', nargs='?')), + (['--force'], + dict(help="force Let's Encrypt certificate issuance", + action='store_true')), (['--dns'], dict(help="choose dns provider api for letsencrypt", action='store' or 'store_const', @@ -796,9 +799,11 @@ class WOSiteCreateController(CementBaseController): else: # check DNS records before issuing cert if not acmedata['dns'] is True: - if not WOAcme.check_dns(self, acme_domains): - Log.error(self, - "Aborting SSL certificate issuance") + if not pargs.force: + if not WOAcme.check_dns(self, acme_domains): + Log.error(self, + "Aborting SSL " + "certificate issuance") Log.debug(self, "Setup Cert with acme.sh for {0}" .format(wo_domain)) if WOAcme.setupletsencrypt( @@ -806,9 +811,10 @@ class WOSiteCreateController(CementBaseController): WOAcme.deploycert(self, wo_domain) else: if not acmedata['dns'] is True: - if not WOAcme.check_dns(self, acme_domains): - Log.error(self, - "Aborting SSL certificate issuance") + if not pargs.force: + if not WOAcme.check_dns(self, acme_domains): + Log.error(self, + "Aborting SSL certificate issuance") if WOAcme.setupletsencrypt( self, acme_domains, acmedata): WOAcme.deploycert(self, wo_domain) @@ -885,6 +891,9 @@ class WOSiteUpdateController(CementBaseController): choices=('on', 'off', 'renew', 'subdomain', 'wildcard', 'clean', 'purge'), const='on', nargs='?')), + (['--force'], + dict(help="force LetsEncrypt certificate issuance/renewal", + action='store_true')), (['--dns'], dict(help="choose dns provider api for letsencrypt", action='store' or 'store_const', @@ -901,9 +910,6 @@ class WOSiteUpdateController(CementBaseController): dict(help="update to proxy site", nargs='+')), (['--all'], dict(help="update all sites", action='store_true')), - (['--force'], - dict(help="force letsencrypt certificate renewal", - action='store_true')), ] @expose(help="Update site type or cache") @@ -1446,10 +1452,13 @@ class WOSiteUpdateController(CementBaseController): else: # check DNS records before issuing cert if not acmedata['dns'] is True: - if not WOAcme.check_dns(self, acme_domains): - Log.error( - self, - "Aborting SSL certificate issuance") + if not pargs.force: + if not WOAcme.check_dns(self, + acme_domains): + Log.error( + self, + "Aborting SSL certificate " + "issuance") Log.debug(self, "Setup Cert with acme.sh for {0}" .format(wo_domain)) if WOAcme.setupletsencrypt( @@ -1460,10 +1469,11 @@ class WOSiteUpdateController(CementBaseController): else: # check DNS records before issuing cert if not acmedata['dns'] is True: - if not WOAcme.check_dns(self, acme_domains): - Log.error( - self, - "Aborting SSL certificate issuance") + if not pargs.force: + if not WOAcme.check_dns(self, acme_domains): + Log.error( + self, + "Aborting SSL certificate issuance") if WOAcme.setupletsencrypt( self, acme_domains, acmedata): WOAcme.deploycert(self, wo_domain) diff --git a/wo/cli/plugins/site_functions.py b/wo/cli/plugins/site_functions.py index 920a4a9..9f2d641 100644 --- a/wo/cli/plugins/site_functions.py +++ b/wo/cli/plugins/site_functions.py @@ -930,16 +930,9 @@ def updatewpuserpassword(self, wo_domain, wo_site_webroot): wo_wp_pass = '' WOFileUtils.chdir(self, '{0}/htdocs/'.format(wo_site_webroot)) - # Check if wo_domain is wordpress install - try: - is_wp = WOShellExec.cmd_exec(self, "wp --allow-root core" - " version") - except CommandExecutionError as e: - Log.debug(self, "{0}".format(e)) - raise SiteError("is WordPress site? check command failed ") - - # Exit if wo_domain is not wordpress install - if not is_wp: + if not WOShellExec.cmd_exec(self, "wp --allow-root core" + " is-installed"): + # Exit if wo_domain is not wordpress install Log.error(self, "{0} does not seem to be a WordPress site" .format(wo_domain)) @@ -1333,8 +1326,6 @@ def doCleanupAction(self, domain='', webroot='', dbname='', dbuser='', if os.path.isfile('/etc/nginx/sites-available/{0}' .format(domain)): removeNginxConf(self, domain) - if os.path.isdir('/etc/letsencrypt/renewal/{0}_ecc' - .format(domain)): removeAcmeConf(self, domain) if webroot: diff --git a/wo/cli/plugins/stack.py b/wo/cli/plugins/stack.py index 32eab17..9a2e3ea 100644 --- a/wo/cli/plugins/stack.py +++ b/wo/cli/plugins/stack.py @@ -129,7 +129,6 @@ class WOStackController(CementBaseController): pargs.php73 = True pargs.redis = True pargs.proftpd = True - pargs.security = True if pargs.web: pargs.nginx = True @@ -152,7 +151,6 @@ class WOStackController(CementBaseController): if pargs.security: pargs.fail2ban = True pargs.clamav = True - pargs.ufw = True # Nginx if pargs.nginx: @@ -261,19 +259,24 @@ class WOStackController(CementBaseController): # UFW if pargs.ufw: - if not WOFileUtils.grep( - self, '/etc/ufw/ufw.conf', 'ENABLED=yes'): - Log.debug(self, "Setting apt_packages variable for UFW") - apt_packages = apt_packages + ["ufw"] + Log.debug(self, "Setting apt_packages variable for UFW") + apt_packages = apt_packages + ["ufw"] # sendmail if pargs.sendmail: Log.debug(self, "Setting apt_packages variable for Sendmail") - if not WOAptGet.is_installed(self, 'sendmail'): + if (not WOAptGet.is_installed(self, 'sendmail') and + not WOAptGet.is_installed(self, 'postfix')): apt_packages = apt_packages + ["sendmail"] else: - Log.debug(self, "Sendmail already installed") - Log.info(self, "Sendmail already installed") + if WOAptGet.is_installed(self, 'sendmail'): + Log.debug(self, "Sendmail already installed") + Log.info(self, "Sendmail already installed") + else: + Log.debug( + self, "Another mta (Postfix) is already installed") + Log.info( + self, "Another mta (Postfix) is already installed") # proftpd if pargs.proftpd: @@ -521,7 +524,6 @@ class WOStackController(CementBaseController): (not pargs.php73)): pargs.web = True pargs.admin = True - pargs.security = True if pargs.all: pargs.web = True diff --git a/wo/cli/plugins/stack_pref.py b/wo/cli/plugins/stack_pref.py index f55f0da..521e2d3 100644 --- a/wo/cli/plugins/stack_pref.py +++ b/wo/cli/plugins/stack_pref.py @@ -43,7 +43,7 @@ def pre_pref(self, apt_packages): WORepo.add_key(self, '0xcbcb082a1bb943db', keyserver='keys.gnupg.net') WORepo.add_key(self, '0xF1656F24C74CD1D8', - keyserver='hkp://keys.gnupg.net') + keyserver='keys.gnupg.net') if "mariadb-server" in apt_packages: # generate random 24 characters root password chars = ''.join(random.sample(string.ascii_letters, 24)) @@ -153,11 +153,7 @@ def post_pref(self, apt_packages, packages, upgrade=False): ngxcnf = '/etc/nginx/conf.d' ngxcom = '/etc/nginx/common' ngxroot = '/var/www/' - if upgrade: - if os.path.isdir('/etc/nginx'): - WOGit.add(self, - ["/etc/nginx"], - msg="Adding Nginx into Git") + WOGit.add(self, ["/etc/nginx"], msg="Adding Nginx into Git") data = dict(tls13=True) WOTemplate.deploy(self, '/etc/nginx/nginx.conf', @@ -490,6 +486,7 @@ def post_pref(self, apt_packages, packages, upgrade=False): WOService.restart_service(self, 'nginx') if set(WOVariables.wo_php).issubset(set(apt_packages)): + WOGit.add(self, ["/etc/php"], msg="Adding PHP into Git") Log.info(self, "Configuring php7.2-fpm") ngxroot = '/var/www/' # Create log directories @@ -527,72 +524,32 @@ def post_pref(self, apt_packages, packages, upgrade=False): "/etc/php/7.2/fpm/php.ini") config.write(configfile) - # Parse /etc/php/7.2/fpm/php-fpm.conf + # Render php-fpm pool template for php7.3 data = dict(pid="/run/php/php7.2-fpm.pid", - error_log="/var/log/php/7.2/fpm.log", + error_log="/var/log/php7.2-fpm.log", include="/etc/php/7.2/fpm/pool.d/*.conf") - Log.debug(self, "writting php7.2 configuration into " - "/etc/php/7.2/fpm/php-fpm.conf") - wo_php_fpm = open('/etc/php/7.2/fpm/php-fpm.conf', - encoding='utf-8', mode='w') - self.app.render((data), 'php-fpm.mustache', out=wo_php_fpm) - wo_php_fpm.close() + WOTemplate.deploy( + self, '/etc/php/7.2/fpm/php-fpm.conf', + 'php-fpm.mustache', data) - if not os.path.isfile('/etc/php/7.2/fpm/pool.d/www.conf.orig'): - WOFileUtils.copyfile(self, '/etc/php/7.2/fpm/pool.d/www.conf', - '/etc/php/7.2/fpm/pool.d/www.conf.orig') - # Parse /etc/php/7.2/fpm/pool.d/www.conf - config = configparser.ConfigParser() - config.read_file(codecs.open('/etc/php/7.2/fpm/' - 'pool.d/www.conf.orig', - "r", "utf8")) - config['www']['ping.path'] = '/ping' - config['www']['pm.status_path'] = '/status' - config['www']['pm.max_requests'] = '1500' - config['www']['pm.max_children'] = '50' - config['www']['pm.start_servers'] = '10' - config['www']['pm.min_spare_servers'] = '5' - config['www']['pm.max_spare_servers'] = '15' - config['www']['request_terminate_timeout'] = '300' - config['www']['pm'] = 'ondemand' - config['www']['chdir'] = '/' - config['www']['prefix'] = '/var/run/php' - config['www']['listen'] = 'php72-fpm.sock' - config['www']['listen.mode'] = '0660' - config['www']['listen.backlog'] = '32768' - config['www']['catch_workers_output'] = 'yes' - with codecs.open('/etc/php/7.2/fpm/pool.d/www.conf', - encoding='utf-8', mode='w') as configfile: - Log.debug(self, "Writing PHP 7.2 configuration into " - "/etc/php/7.2/fpm/pool.d/www.conf") - config.write(configfile) - - with open("/etc/php/7.2/fpm/pool.d/www.conf", - encoding='utf-8', mode='a') as myfile: - myfile.write("\nphp_admin_value[open_basedir] " - "= \"/var/www/:/usr/share/php/:" - "/tmp/:/var/run/nginx-cache/:" - "/dev/shm:/dev/urandom\"\n") - - # Generate /etc/php/7.2/fpm/pool.d/www-two.conf - WOFileUtils.copyfile(self, "/etc/php/7.2/fpm/pool.d/www.conf", - "/etc/php/7.2/fpm/pool.d/www-two.conf") - WOFileUtils.searchreplace(self, "/etc/php/7.2/fpm/pool.d/" - "www-two.conf", "[www]", "[www-two]") - config = configparser.ConfigParser() - config.read('/etc/php/7.2/fpm/pool.d/www-two.conf') - config['www-two']['listen'] = 'php72-two-fpm.sock' - with open('/etc/php/7.2/fpm/pool.d/www-two.conf', - encoding='utf-8', mode='w') as confifile: - Log.debug(self, "writting PHP7.2 configuration into " - "/etc/php/7.2/fpm/pool.d/www-two.conf") - config.write(confifile) + data = dict(pool='www-php72', listen='php72-fpm.sock', + user='www-data', + group='www-data', listenuser='root', + listengroup='www-data', openbasedir=True) + WOTemplate.deploy(self, '/etc/php/7.2/fpm/pool.d/www.conf', + 'php-pool.mustache', data) + data = dict(pool='www-two-php72', listen='php72-two-fpm.sock', + user='www-data', + group='www-data', listenuser='root', + listengroup='www-data', openbasedir=True) + WOTemplate.deploy(self, '/etc/php/7.2/fpm/pool.d/www-two.conf', + 'php-pool.mustache', data) # Generate /etc/php/7.2/fpm/pool.d/debug.conf WOFileUtils.copyfile(self, "/etc/php/7.2/fpm/pool.d/www.conf", "/etc/php/7.2/fpm/pool.d/debug.conf") WOFileUtils.searchreplace(self, "/etc/php/7.2/fpm/pool.d/" - "debug.conf", "[www]", "[debug]") + "debug.conf", "[www-php72]", "[debug]") config = configparser.ConfigParser() config.read('/etc/php/7.2/fpm/pool.d/debug.conf') config['debug']['listen'] = '127.0.0.1:9172' @@ -663,6 +620,7 @@ def post_pref(self, apt_packages, packages, upgrade=False): # PHP7.3 configuration if set(WOVariables.wo_php73).issubset(set(apt_packages)): + WOGit.add(self, ["/etc/php"], msg="Adding PHP into Git") Log.info(self, "Configuring php7.3-fpm") ngxroot = '/var/www/' # Create log directories @@ -700,72 +658,32 @@ def post_pref(self, apt_packages, packages, upgrade=False): "/etc/php/7.3/fpm/php.ini") config.write(configfile) - # Parse /etc/php/7.3/fpm/php-fpm.conf + # Render php-fpm pool template for php7.3 data = dict(pid="/run/php/php7.3-fpm.pid", error_log="/var/log/php7.3-fpm.log", include="/etc/php/7.3/fpm/pool.d/*.conf") - Log.debug(self, "writting php 7.3 configuration into " - "/etc/php/7.3/fpm/php-fpm.conf") - wo_php_fpm = open('/etc/php/7.3/fpm/php-fpm.conf', - encoding='utf-8', mode='w') - self.app.render((data), 'php-fpm.mustache', out=wo_php_fpm) - wo_php_fpm.close() + WOTemplate.deploy( + self, '/etc/php/7.3/fpm/php-fpm.conf', + 'php-fpm.mustache', data) - # Parse /etc/php/7.3/fpm/pool.d/www.conf - if not os.path.isfile('/etc/php/7.3/fpm/pool.d/www.conf.orig'): - WOFileUtils.copyfile(self, '/etc/php/7.3/fpm/pool.d/www.conf', - '/etc/php/7.3/fpm/pool.d/www.conf.orig') - config = configparser.ConfigParser() - config.read_file(codecs.open('/etc/php/7.3/fpm/' - 'pool.d/www.conf.orig', - "r", "utf8")) - config['www']['ping.path'] = '/ping' - config['www']['pm.status_path'] = '/status' - config['www']['pm.max_requests'] = '1500' - config['www']['pm.max_children'] = '50' - config['www']['pm.start_servers'] = '10' - config['www']['pm.min_spare_servers'] = '5' - config['www']['pm.max_spare_servers'] = '15' - config['www']['request_terminate_timeout'] = '300' - config['www']['pm'] = 'ondemand' - config['www']['chdir'] = '/' - config['www']['prefix'] = '/var/run/php' - config['www']['listen'] = 'php73-fpm.sock' - config['www']['listen.mode'] = '0660' - config['www']['listen.backlog'] = '32768' - config['www']['catch_workers_output'] = 'yes' - with codecs.open('/etc/php/7.3/fpm/pool.d/www.conf', - encoding='utf-8', mode='w') as configfile: - Log.debug(self, "writting PHP 7.3 configuration into " - "/etc/php/7.3/fpm/pool.d/www.conf") - config.write(configfile) - - with open("/etc/php/7.3/fpm/pool.d/www.conf", - encoding='utf-8', mode='a') as myfile: - myfile.write("\nphp_admin_value[open_basedir] " - "= \"/var/www/:/usr/share/php/:" - "/tmp/:/var/run/nginx-cache/:" - "/dev/shm:/dev/urandom\"\n") - - # Generate /etc/php/7.3/fpm/pool.d/www-two.conf - WOFileUtils.copyfile(self, "/etc/php/7.3/fpm/pool.d/www.conf", - "/etc/php/7.3/fpm/pool.d/www-two.conf") - WOFileUtils.searchreplace(self, "/etc/php/7.3/fpm/pool.d/" - "www-two.conf", "[www]", "[www-two]") - config = configparser.ConfigParser() - config.read('/etc/php/7.3/fpm/pool.d/www-two.conf') - config['www-two']['listen'] = 'php73-two-fpm.sock' - with open('/etc/php/7.3/fpm/pool.d/www-two.conf', - encoding='utf-8', mode='w') as confifile: - Log.debug(self, "writting PHP7.3 configuration into " - "/etc/php/7.3/fpm/pool.d/www-two.conf") - config.write(confifile) + data = dict(pool='www-php73', listen='php73-fpm.sock', + user='www-data', + group='www-data', listenuser='root', + listengroup='www-data', openbasedir=True) + WOTemplate.deploy(self, '/etc/php/7.3/fpm/pool.d/www.conf', + 'php-pool.mustache', data) + data = dict(pool='www-two-php73', listen='php73-two-fpm.sock', + user='www-data', + group='www-data', listenuser='root', + listengroup='www-data', openbasedir=True) + WOTemplate.deploy(self, '/etc/php/7.3/fpm/pool.d/www-two.conf', + 'php-pool.mustache', data) # Generate /etc/php/7.3/fpm/pool.d/debug.conf WOFileUtils.copyfile(self, "/etc/php/7.3/fpm/pool.d/www.conf", "/etc/php/7.3/fpm/pool.d/debug.conf") WOFileUtils.searchreplace(self, "/etc/php/7.3/fpm/pool.d/" - "debug.conf", "[www]", "[debug]") + "debug.conf", "[www-php73]", "[debug]") config = configparser.ConfigParser() config.read('/etc/php/7.3/fpm/pool.d/debug.conf') config['debug']['listen'] = '127.0.0.1:9173' @@ -836,6 +754,7 @@ def post_pref(self, apt_packages, packages, upgrade=False): # create mysql config if it doesn't exist if "mariadb-server" in apt_packages: + WOGit.add(self, ["/etc/mysql"], msg="Adding MySQL into Git") if not os.path.isfile("/etc/mysql/my.cnf"): config = ("[mysqld]\nwait_timeout = 30\n" "interactive_timeout=60\nperformance_schema = 0" @@ -889,6 +808,8 @@ def post_pref(self, apt_packages, packages, upgrade=False): # create fail2ban configuration files if set(WOVariables.wo_fail2ban).issubset(set(apt_packages)): + WOGit.add(self, ["/etc/fail2ban"], + msg="Adding Fail2ban into Git") if not os.path.isfile("/etc/fail2ban/jail.d/custom.conf"): Log.info(self, "Configuring Fail2Ban") data = dict() @@ -914,6 +835,8 @@ def post_pref(self, apt_packages, packages, upgrade=False): # Proftpd configuration if "proftpd-basic" in apt_packages: + WOGit.add(self, ["/etc/proftpd"], + msg="Adding ProFTPd into Git") if os.path.isfile("/etc/proftpd/proftpd.conf"): Log.info(self, "Configuring ProFTPd") Log.debug(self, "Setting up Proftpd configuration") @@ -936,32 +859,28 @@ def post_pref(self, apt_packages, packages, upgrade=False): WOFileUtils.chmod(self, "/etc/proftpd/ssl/proftpd.key", 0o700) WOFileUtils.chmod(self, "/etc/proftpd/ssl/proftpd.crt", 0o700) data = dict() - Log.debug(self, 'Writting the proftpd configuration to ' - 'file /etc/proftpd/tls.conf') - wo_proftpdconf = open('/etc/proftpd/tls.conf', - encoding='utf-8', mode='w') - self.app.render((data), 'proftpd-tls.mustache', - out=wo_proftpdconf) - wo_proftpdconf.close() + WOTemplate.deploy(self, '/etc/proftpd/tls.conf', + 'proftpd-tls.mustache', data) WOFileUtils.searchreplace(self, "/etc/proftpd/" "proftpd.conf", "#Include /etc/proftpd/tls.conf", "Include /etc/proftpd/tls.conf") WOService.restart_service(self, 'proftpd') - # add rule for proftpd with UFW - if WOFileUtils.grepcheck( - self, '/etc/ufw/ufw.conf', 'ENABLED=yes'): - try: - WOShellExec.cmd_exec( - self, "ufw limit 21") - WOShellExec.cmd_exec( - self, "ufw allow 49000:50000/tcp") - WOShellExec.cmd_exec( - self, "ufw reload") - except CommandExecutionError as e: - Log.debug(self, "{0}".format(e)) - Log.error(self, "Unable to add UFW rule") + if os.path.isfile('/etc/ufw/ufw.conf'): + # add rule for proftpd with UFW + if WOFileUtils.grepcheck( + self, '/etc/ufw/ufw.conf', 'ENABLED=yes'): + try: + WOShellExec.cmd_exec( + self, "ufw limit 21") + WOShellExec.cmd_exec( + self, "ufw allow 49000:50000/tcp") + WOShellExec.cmd_exec( + self, "ufw reload") + except Exception as e: + Log.debug(self, "{0}".format(e)) + Log.error(self, "Unable to add UFW rules") if ((os.path.isfile("/etc/fail2ban/jail.d/custom.conf")) and (not WOFileUtils.grep( @@ -1021,6 +940,8 @@ def post_pref(self, apt_packages, packages, upgrade=False): # set maxmemory 10% for ram below 512MB and 20% for others # set maxmemory-policy allkeys-lru # enable systemd service + WOGit.add(self, ["/etc/redis"], + msg="Adding Redis into Git") Log.debug(self, "Enabling redis systemd service") WOShellExec.cmd_exec(self, "systemctl enable redis-server") if (os.path.isfile("/etc/redis/redis.conf") and @@ -1029,7 +950,7 @@ def post_pref(self, apt_packages, packages, upgrade=False): Log.wait(self, "Tuning Redis configuration") with open("/etc/redis/redis.conf", "a") as redis_file: - redis_file.write("\n# WordOps v3.9.8\n") + redis_file.write("\n# WordOps v3.9.9\n") wo_ram = psutil.virtual_memory().total / (1024 * 1024) if wo_ram < 1024: Log.debug(self, "Setting maxmemory variable to " @@ -1069,8 +990,10 @@ def post_pref(self, apt_packages, packages, upgrade=False): "tcp-backlog 32768") WOFileUtils.chown(self, '/etc/redis/redis.conf', 'redis', 'redis', recursive=False) - WOService.restart_service(self, 'redis-server') Log.valide(self, "Tuning Redis configuration") + WOGit.add(self, ["/etc/redis"], + msg="Adding Redis into Git") + WOService.restart_service(self, 'redis-server') # ClamAV configuration if set(WOVariables.wo_clamav).issubset(set(apt_packages)): @@ -1405,13 +1328,10 @@ def post_pref(self, apt_packages, packages, upgrade=False): Log.debug(self, "configration Anemometer") data = dict(host=WOVariables.wo_mysql_host, port='3306', user='anemometer', password=chars) - wo_anemometer = open('{0}22222/htdocs/db/anemometer' - '/conf/config.inc.php' - .format(WOVariables.wo_webroot), - encoding='utf-8', mode='w') - self.app.render((data), 'anemometer.mustache', - out=wo_anemometer) - wo_anemometer.close() + WOTemplate.deploy(self, '{0}22222/htdocs/db/anemometer' + '/conf/config.inc.php' + .format(WOVariables.wo_webroot), + 'anemometer.mustache', data) # pt-query-advisor if any('/usr/bin/pt-query-advisor' == x[1] diff --git a/wo/cli/plugins/stack_upgrade.py b/wo/cli/plugins/stack_upgrade.py index 77cbd28..fe4eeca 100644 --- a/wo/cli/plugins/stack_upgrade.py +++ b/wo/cli/plugins/stack_upgrade.py @@ -212,7 +212,6 @@ class WOStackUpgradeController(CementBaseController): if ["php7.3-fpm"] in apt_packages: WOAptGet.remove(self, ['php7.3-fpm'], auto=False, purge=True) - # check if nginx upgrade is blocked if os.path.isfile( '/etc/apt/preferences.d/nginx-block'): diff --git a/wo/cli/templates/locations.mustache b/wo/cli/templates/locations.mustache index ff9da19..0d9a88a 100644 --- a/wo/cli/templates/locations.mustache +++ b/wo/cli/templates/locations.mustache @@ -12,7 +12,7 @@ location @empty_gif { } # Cache static files location ~* \.(ogg|ogv|svg|svgz|eot|otf|woff|woff2|ttf|m4a|mp4|ttf|rss|atom|jpe?g|gif|cur|heic|png|tiff|ico|webm|mp3|aac|tgz|doc|xls|exe|ppt|tar|mid|midi|wav|bmp|rtf|swf|webp|json|webmanifest)$ { - more_set_headers 'Access-Control-Allow-Origin : "*"'; + more_set_headers 'Access-Control-Allow-Origin : *'; more_set_headers "Cache-Control : public, no-transform"; access_log off; log_not_found off; @@ -20,7 +20,7 @@ location ~* \.(ogg|ogv|svg|svgz|eot|otf|woff|woff2|ttf|m4a|mp4|ttf|rss|atom|jpe? } # Cache css & js files location ~* \.(?:css(\.map)?|js(\.map)?)$ { - more_set_headers 'Access-Control-Allow-Origin : "*"'; + more_set_headers 'Access-Control-Allow-Origin : *'; more_set_headers "Cache-Control : public, no-transform"; access_log off; log_not_found off; diff --git a/wo/cli/templates/nginx-core.mustache b/wo/cli/templates/nginx-core.mustache index dd1db4b..096a599 100644 --- a/wo/cli/templates/nginx-core.mustache +++ b/wo/cli/templates/nginx-core.mustache @@ -66,7 +66,7 @@ http { more_set_headers "X-Frame-Options : SAMEORIGIN"; more_set_headers "X-Xss-Protection : 1; mode=block"; more_set_headers "X-Content-Type-Options : nosniff"; - more_set_headers "Referrer-Policy : strict-origin-when-cross-origin"; + more_set_headers "Referrer-Policy : no-referrer, strict-origin-when-cross-origin"; more_set_headers "X-Download-Options : noopen"; # oscp settings diff --git a/wo/cli/templates/php-pool.mustache b/wo/cli/templates/php-pool.mustache new file mode 100644 index 0000000..e153bf0 --- /dev/null +++ b/wo/cli/templates/php-pool.mustache @@ -0,0 +1,23 @@ +[{{pool}}] +user = {{user}} +group = {{group}} +listen = {{listen}} +listen.owner = {{listenuser}} +listen.group = {{listengroup}} +pm = ondemand +pm.max_children = 50 +pm.start_servers = 10 +pm.min_spare_servers = 5 +pm.max_spare_servers = 15 +ping.path = /ping +pm.status_path = /status +pm.max_requests = 1500 +request_terminate_timeout = 300 +chdir = / +prefix = /var/run/php +listen.mode = 0660 +listen.backlog = 32768 +catch_workers_output = yes + + +{{#openbasedir}}php_admin_value[open_basedir] = "/var/www/:/usr/share/php/:/tmp/:/var/run/nginx-cache/"{{/openbasedir}} diff --git a/wo/cli/templates/sshd.mustache b/wo/cli/templates/sshd.mustache index 2266ee2..f05b6c8 100644 --- a/wo/cli/templates/sshd.mustache +++ b/wo/cli/templates/sshd.mustache @@ -33,8 +33,11 @@ X11Forwarding yes # Allow client to pass locale environment variables AcceptEnv LANG LC_* -# override default of no subsystems -Subsystem sftp /usr/lib/openssh/sftp-server +# LogLevel VERBOSE logs user's key fingerprint on login. Needed to have a clear audit track of which key was using to log in. +LogLevel VERBOSE + +# Log sftp level file access (read/write/etc.) that would not be easily logged otherwise. +Subsystem sftp /usr/lib/ssh/sftp-server -f AUTHPRIV -l INFO # Host keys the client accepts - order here is honored by OpenSSH HostKeyAlgorithms ssh-ed25519-cert-v01@openssh.com,ssh-rsa-cert-v01@openssh.com,ssh-ed25519,ssh-rsa,ecdsa-sha2-nistp521-cert-v01@openssh.com,ecdsa-sha2-nistp384-cert-v01@openssh.com,ecdsa-sha2-nistp256-cert-v01@openssh.com,ecdsa-sha2-nistp521,ecdsa-sha2-nistp384,ecdsa-sha2-nistp256 @@ -42,4 +45,8 @@ HostKeyAlgorithms ssh-ed25519-cert-v01@openssh.com,ssh-rsa-cert-v01@openssh.com, # use strong ciphers KexAlgorithms curve25519-sha256@libssh.org,ecdh-sha2-nistp521,ecdh-sha2-nistp384,ecdh-sha2-nistp256,diffie-hellman-group-exchange-sha256 Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr -MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-512,hmac-sha2-256,umac-128@openssh.com \ No newline at end of file +MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-512,hmac-sha2-256,umac-128@openssh.com + +# Use kernel sandbox mechanisms where possible in unprivileged processes +# Systrace on OpenBSD, Seccomp on Linux, seatbelt on MacOSX/Darwin, rlimit elsewhere. +UsePrivilegeSeparation sandbox \ No newline at end of file diff --git a/wo/cli/templates/wpcommon.mustache b/wo/cli/templates/wpcommon.mustache index ccc10d7..d9a3cd0 100644 --- a/wo/cli/templates/wpcommon.mustache +++ b/wo/cli/templates/wpcommon.mustache @@ -40,15 +40,15 @@ location @robots { location /wp-content/uploads { location ~ \.(png|jpe?g)$ { add_header Vary "Accept-Encoding"; - add_header "Access-Control-Allow-Origin" "*"; + more_set_headers '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 + location ~* \.(php|gz|log|zip|tar|rar)$ { + #Prevent Direct Access Of PHP Files & BackupsFrom Web Browsers deny all; } } @@ -56,7 +56,7 @@ location /wp-content/uploads { location /wp-content/plugins/ewww-image-optimizer/images { location ~ \.(png|jpe?g)$ { add_header Vary "Accept-Encoding"; - add_header "Access-Control-Allow-Origin" "*"; + more_set_headers 'Access-Control-Allow-Origin : *'; add_header Cache-Control "public, no-transform"; access_log off; log_not_found off; @@ -72,7 +72,7 @@ location /wp-content/plugins/ewww-image-optimizer/images { location /wp-content/cache { # Cache css & js files location ~* \.(?:css(\.map)?|js(\.map)?|.html)$ { - add_header "Access-Control-Allow-Origin" "*"; + more_set_headers 'Access-Control-Allow-Origin : *'; access_log off; log_not_found off; expires 30d; diff --git a/wo/core/aptget.py b/wo/core/aptget.py index e41a4f6..d3c2a81 100644 --- a/wo/core/aptget.py +++ b/wo/core/aptget.py @@ -18,7 +18,7 @@ class WOAptGet(): """ try: with open('/var/log/wo/wordops.log', 'a') as f: - proc = subprocess.Popen('apt-get update', + proc = subprocess.Popen('apt-mirror-updater -u', shell=True, stdin=None, stdout=f, stderr=subprocess.PIPE, diff --git a/wo/core/fileutils.py b/wo/core/fileutils.py index 0cf8bcb..ca3ae00 100644 --- a/wo/core/fileutils.py +++ b/wo/core/fileutils.py @@ -202,6 +202,37 @@ class WOFileUtils(): Log.debug(self, "{0}".format(e.strerror)) Log.error(self, "Unable to change owner : {0}".format(path)) + def wpperm(self, path, harden=False): + """ + Fix WordPress site permissions + path : WordPress site path + harden : set 750/640 instead of 755/644 + """ + userid = pwd.getpwnam('www-data')[2] + groupid = pwd.getpwnam('www-data')[3] + try: + Log.debug(self, "Fixing WordPress permissions of {0}" + .format(path)) + if harden: + dperm = '0o750' + fperm = '0o640' + else: + dperm = '0o755' + fperm = '0o644' + + for root, dirs, files in os.walk(path): + for d in dirs: + os.chown(os.path.join(root, d), userid, + groupid) + os.chmod(os.path.join(root, d), dperm) + for f in files: + os.chown(os.path.join(root, d), userid, + groupid) + os.chmod(os.path.join(root, f), fperm) + except OSError as e: + Log.debug(self, "{0}".format(e.strerror)) + Log.error(self, "Unable to change owner : {0}".format(path)) + def mkdir(self, path): """ create directories. diff --git a/wo/core/git.py b/wo/core/git.py index 96a9638..72a86f6 100644 --- a/wo/core/git.py +++ b/wo/core/git.py @@ -74,7 +74,7 @@ class WOGit: try: Log.debug(self, "WOGit: git reset HEAD~ at {0}" .format(path)) - git.reset("--hard HEAD~") + git.reset("HEAD~", "--hard") except ErrorReturnCode as e: Log.debug(self, "{0}".format(e)) Log.error(self, "Unable to git reset at {0} " diff --git a/wo/core/variables.py b/wo/core/variables.py index af40727..d0154c0 100644 --- a/wo/core/variables.py +++ b/wo/core/variables.py @@ -11,10 +11,10 @@ class WOVariables(): """Intialization of core variables""" # WordOps version - wo_version = "3.9.9" + wo_version = "3.9.9.1" # WordOps packages versions wo_wp_cli = "2.3.0" - wo_adminer = "4.7.2" + wo_adminer = "4.7.3" wo_phpmyadmin = "4.9.1" wo_extplorer = "2.1.13" wo_dashboard = "1.2"