Files
WPIQ/wo/cli/plugins/secure.py

364 lines
15 KiB
Python
Raw Normal View History

2019-09-04 20:36:15 +02:00
import getpass
2019-09-23 12:11:15 +02:00
import os
import shutil
2019-09-04 20:36:15 +02:00
from cement.core.controller import CementBaseController, expose
2019-10-16 13:31:30 +02:00
from wo.core.fileutils import WOFileUtils
2018-11-13 21:55:59 +01:00
from wo.core.git import WOGit
2019-09-04 20:36:15 +02:00
from wo.core.logging import Log
2019-09-22 17:00:35 +02:00
from wo.core.random import RANDOM
2018-11-13 21:55:59 +01:00
from wo.core.services import WOService
2019-09-04 20:36:15 +02:00
from wo.core.shellexec import WOShellExec
2019-09-23 12:11:15 +02:00
from wo.core.template import WOTemplate
2019-10-02 13:13:32 +02:00
from wo.core.variables import WOVar
2018-11-13 21:55:59 +01:00
def wo_secure_hook(app):
pass
class WOSecureController(CementBaseController):
class Meta:
label = 'secure'
stacked_on = 'base'
stacked_type = 'nested'
2019-09-19 14:07:34 +02:00
description = (
2021-04-10 20:19:40 +10:00
'Secure command provide the ability to '
2019-09-19 14:07:34 +02:00
'adjust settings for backend and to harden server security.')
2018-11-13 21:55:59 +01:00
arguments = [
(['--auth'],
2019-09-19 14:07:34 +02:00
dict(help='secure backend authentification',
action='store_true')),
2018-11-13 21:55:59 +01:00
(['--port'],
2019-09-19 14:07:34 +02:00
dict(help='set backend port', action='store_true')),
2018-11-13 21:55:59 +01:00
(['--ip'],
2019-09-19 14:07:34 +02:00
dict(help='set backend whitelisted ip', action='store_true')),
2019-09-23 12:11:15 +02:00
(['--sshport'], dict(
2019-09-21 19:12:46 +02:00
help='set custom ssh port', action='store_true')),
2019-09-23 12:11:15 +02:00
(['--ssh'], dict(
help='harden ssh security', action='store_true')),
2019-09-26 15:45:38 +02:00
(['--allowpassword'], dict(
help='allow password authentification '
'when hardening ssh security', action='store_true')),
(['--lockdown'], dict(
help='enable WP Fort Knox lockdown on a site',
action='store_true')),
(['--unlock'], dict(
help='disable WP Fort Knox lockdown on a site',
action='store_true')),
2019-09-24 02:44:33 +02:00
(['--force'],
dict(help='force execution without being prompt',
2019-09-21 19:12:46 +02:00
action='store_true')),
2018-11-13 21:55:59 +01:00
(['user_input'],
dict(help='user input', nargs='?', default=None)),
(['user_pass'],
dict(help='user pass', nargs='?', default=None))]
usage = "wo secure [options]"
@expose(hide=True)
def default(self):
2019-08-07 02:45:26 +02:00
pargs = self.app.pargs
if pargs.auth:
2019-03-19 16:58:35 +01:00
self.secure_auth()
2019-08-07 02:45:26 +02:00
if pargs.port:
2019-03-19 16:58:35 +01:00
self.secure_port()
2019-08-07 02:45:26 +02:00
if pargs.ip:
2019-03-19 16:58:35 +01:00
self.secure_ip()
2019-09-23 12:11:15 +02:00
if pargs.sshport:
self.secure_ssh_port()
if pargs.ssh:
self.secure_ssh()
if pargs.lockdown:
self.secure_lockdown()
if pargs.unlock:
self.secure_unlock()
2018-11-13 21:55:59 +01:00
@expose(hide=True)
def secure_auth(self):
"""This function secures authentication"""
WOGit.add(self, [WOVar.wo_ols_conf_dir],
msg="Add OLS config to Git")
2019-08-07 02:45:26 +02:00
pargs = self.app.pargs
2019-10-08 11:17:07 +02:00
passwd = RANDOM.long(self)
2019-08-07 02:45:26 +02:00
if not pargs.user_input:
2018-11-13 21:55:59 +01:00
username = input("Provide HTTP authentication user "
2019-10-02 13:13:32 +02:00
"name [{0}] :".format(WOVar.wo_user))
2019-08-07 02:45:26 +02:00
pargs.user_input = username
2018-11-13 21:55:59 +01:00
if username == "":
2019-10-02 13:13:32 +02:00
pargs.user_input = WOVar.wo_user
2019-08-07 02:45:26 +02:00
if not pargs.user_pass:
2018-11-13 21:55:59 +01:00
password = getpass.getpass("Provide HTTP authentication "
"password [{0}] :".format(passwd))
2019-08-07 02:45:26 +02:00
pargs.user_pass = password
2018-11-13 21:55:59 +01:00
if password == "":
2019-08-07 02:45:26 +02:00
pargs.user_pass = passwd
# Set OLS admin + backend password directly
# (admpass.sh is interactive-only and hangs in automation)
WOShellExec.cmd_exec(
self, "printf \"{username}:"
"$(openssl passwd -apr1 '{password}' "
"2>/dev/null)\n\" "
"> /usr/local/lsws/admin/conf/htpasswd "
"2>/dev/null"
.format(username=pargs.user_input,
password=pargs.user_pass),
log=False)
WOShellExec.cmd_exec(
self, "printf \"{username}:"
"$(openssl passwd -apr1 '{password}' "
"2>/dev/null)\n\" "
"> {conf}/htpasswd-wo "
"2>/dev/null"
.format(username=pargs.user_input,
password=pargs.user_pass,
conf=WOVar.wo_ols_conf_dir),
log=False)
WOGit.add(self, [WOVar.wo_ols_conf_dir],
2018-11-13 21:55:59 +01:00
msg="Adding changed secure auth into Git")
@expose(hide=True)
def secure_port(self):
"""This function Secures port"""
WOGit.add(self, [WOVar.wo_ols_conf_dir],
msg="Add OLS config to Git")
2019-08-07 02:45:26 +02:00
pargs = self.app.pargs
if pargs.user_input:
2019-09-23 13:09:15 +02:00
while ((not pargs.user_input.isdigit()) and
2019-09-23 14:24:16 +02:00
(not pargs.user_input < 65536)):
2018-11-13 21:55:59 +01:00
Log.info(self, "Please enter a valid port number ")
2019-08-07 02:45:26 +02:00
pargs.user_input = input("WordOps "
2019-09-05 11:47:04 +02:00
"admin port [22222]:")
2019-10-07 13:11:38 +02:00
else:
2018-11-13 21:55:59 +01:00
port = input("WordOps admin port [22222]:")
if port == "":
2019-09-23 13:09:15 +02:00
port = 22222
2019-10-07 13:11:38 +02:00
while ((not port.isdigit()) and (not port != "") and
(not port < 65536)):
2018-11-13 21:55:59 +01:00
Log.info(self, "Please Enter valid port number :")
port = input("WordOps admin port [22222]:")
2019-08-07 02:45:26 +02:00
pargs.user_input = port
# Update OLS backend listener port
httpd_conf = '{0}/httpd_config.conf'.format(WOVar.wo_ols_conf_dir)
if os.path.isfile(httpd_conf):
WOFileUtils.searchreplace(
self, httpd_conf,
'address *:22222',
'address *:{0}'.format(pargs.user_input))
WOGit.add(self, [WOVar.wo_ols_conf_dir],
2018-11-13 21:55:59 +01:00
msg="Adding changed secure port into Git")
if not WOService.reload_service(self, 'lsws'):
Log.error(self, "service lsws reload failed. "
"check issues with `{0} -t` command"
.format(WOVar.wo_ols_bin))
2018-11-13 21:55:59 +01:00
Log.info(self, "Successfully port changed {port}"
2019-08-07 02:45:26 +02:00
.format(port=pargs.user_input))
2018-11-13 21:55:59 +01:00
@expose(hide=True)
def secure_ip(self):
"""IP whitelisting"""
WOGit.add(self, [WOVar.wo_ols_conf_dir],
msg="Add OLS config to Git")
2019-08-07 02:45:26 +02:00
pargs = self.app.pargs
if not pargs.user_input:
2018-11-13 21:55:59 +01:00
ip = input("Enter the comma separated IP addresses "
"to white list [127.0.0.1]:")
2019-08-07 02:45:26 +02:00
pargs.user_input = ip
2018-11-13 21:55:59 +01:00
try:
user_ip = pargs.user_input.strip().split(',')
2018-11-13 21:55:59 +01:00
except Exception as e:
2019-07-29 15:08:49 +02:00
Log.debug(self, "{0}".format(e))
2018-11-13 21:55:59 +01:00
user_ip = ['127.0.0.1']
# Update OLS ACL configuration
acl_conf = '{0}/22222/vhconf.conf'.format(WOVar.wo_ols_vhost_dir)
if os.path.isfile(acl_conf):
for ip_addr in user_ip:
ip_addr = ip_addr.strip()
if not WOFileUtils.grepcheck(self, acl_conf, ip_addr):
WOFileUtils.searchreplace(
self, acl_conf,
'allowList',
'allowList\n {0}'.format(ip_addr))
WOGit.add(self, [WOVar.wo_ols_conf_dir],
2018-11-13 21:55:59 +01:00
msg="Adding changed secure ip into Git")
Log.info(self, "Successfully added IP address in access control")
2018-11-13 21:55:59 +01:00
@expose(hide=True)
def secure_lockdown(self):
"""Enable WP Fort Knox lockdown on a WordPress site"""
pargs = self.app.pargs
if not pargs.user_input:
site_name = input("Enter the site name to lockdown: ")
pargs.user_input = site_name
site_name = pargs.user_input
webroot = '{0}{1}'.format(WOVar.wo_webroot, site_name)
mu_plugins_dir = '{0}/htdocs/wp-content/mu-plugins'.format(webroot)
fort_knox_src = '/var/lib/wo/wp-fort-knox.php'
if not os.path.isdir(webroot):
Log.error(self, "Site {0} not found".format(site_name))
# Check if it's a WordPress site
if not os.path.isfile(
'{0}/htdocs/wp-config.php'.format(webroot)):
Log.error(self, "Site {0} is not a WordPress site"
.format(site_name))
# Check if Fort Knox source exists
if not os.path.isfile(fort_knox_src):
Log.error(self, "WP Fort Knox plugin not found at {0}. "
"Please reinstall WordOps.".format(fort_knox_src))
# Create mu-plugins directory if it doesn't exist
if not os.path.isdir(mu_plugins_dir):
WOFileUtils.mkdir(self, mu_plugins_dir)
fort_knox_dest = '{0}/wp-fort-knox.php'.format(mu_plugins_dir)
if os.path.isfile(fort_knox_dest):
Log.info(self, "WP Fort Knox is already enabled for {0}"
.format(site_name))
return
Log.wait(self, "Enabling WP Fort Knox lockdown")
shutil.copy2(fort_knox_src, fort_knox_dest)
WOFileUtils.chown(
self, fort_knox_dest,
WOVar.wo_php_user, WOVar.wo_php_user)
Log.valide(self, "Enabling WP Fort Knox lockdown")
Log.info(self, "WP Fort Knox enabled for {0}\n"
" File modifications and plugin management "
"are now disabled in wp-admin.\n"
" Use WP-CLI for all administrative tasks.\n"
" To disable: wo secure --unlock {0}"
.format(site_name))
@expose(hide=True)
def secure_unlock(self):
"""Disable WP Fort Knox lockdown on a WordPress site"""
pargs = self.app.pargs
if not pargs.user_input:
site_name = input("Enter the site name to unlock: ")
pargs.user_input = site_name
site_name = pargs.user_input
webroot = '{0}{1}'.format(WOVar.wo_webroot, site_name)
fort_knox_path = ('{0}/htdocs/wp-content/mu-plugins/'
'wp-fort-knox.php'.format(webroot))
if not os.path.isdir(webroot):
Log.error(self, "Site {0} not found".format(site_name))
if not os.path.isfile(fort_knox_path):
Log.info(self, "WP Fort Knox is not enabled for {0}"
.format(site_name))
return
Log.wait(self, "Disabling WP Fort Knox lockdown")
WOFileUtils.rm(self, fort_knox_path)
Log.valide(self, "Disabling WP Fort Knox lockdown")
Log.info(self, "WP Fort Knox disabled for {0}\n"
" Plugin management is now available in wp-admin."
.format(site_name))
2018-11-13 21:55:59 +01:00
2019-09-23 12:11:15 +02:00
@expose(hide=True)
def secure_ssh(self):
"""Harden ssh security"""
2019-09-24 02:44:33 +02:00
pargs = self.app.pargs
2019-09-26 15:45:38 +02:00
if not pargs.force and not pargs.allowpassword:
2019-09-24 02:44:33 +02:00
start_secure = input('Are you sure you to want to'
' harden SSH security ?'
'\nSSH login with password will not '
'be possible anymore. Please make sure '
'you are already using SSH Keys.\n'
'Harden SSH security [y/N]')
if start_secure != "Y" and start_secure != "y":
Log.error(self, "Not hardening SSH security")
if os.path.exists('/etc/ssh'):
WOGit.add(self, ["/etc/ssh"],
msg="Adding SSH into Git")
2019-09-23 12:38:16 +02:00
Log.debug(self, "check if /etc/ssh/sshd_config exist")
2019-09-23 12:35:11 +02:00
if os.path.isfile('/etc/ssh/sshd_config'):
2019-09-23 12:38:16 +02:00
Log.debug(self, "looking for the current ssh port")
2019-09-23 12:35:11 +02:00
for line in open('/etc/ssh/sshd_config', encoding='utf-8'):
if 'Port' in line:
ssh_line = line.strip()
2019-09-23 12:11:15 +02:00
break
2019-09-23 12:42:58 +02:00
port = (ssh_line).split(' ')
current_ssh_port = (port[1]).strip()
2019-09-23 13:46:31 +02:00
if os.getenv('SUDO_USER'):
2019-09-23 15:43:23 +02:00
sudo_user = os.getenv('SUDO_USER')
2019-09-23 13:46:31 +02:00
else:
sudo_user = ''
2019-09-26 15:45:38 +02:00
if pargs.allowpassword:
wo_allowpassword = 'yes'
else:
wo_allowpassword = 'no'
data = dict(sshport=current_ssh_port, allowpass=wo_allowpassword,
2019-09-23 13:46:31 +02:00
user=sudo_user)
2019-09-23 16:35:20 +02:00
WOTemplate.deploy(self, '/etc/ssh/sshd_config',
2019-09-23 12:42:58 +02:00
'sshd.mustache', data)
2019-09-23 13:09:15 +02:00
WOGit.add(self, ["/etc/ssh"],
msg="Adding changed SSH port into Git")
if not WOService.restart_service(self, 'ssh'):
Log.error(self, "service SSH restart failed.")
Log.info(self, "Successfully harden SSH security")
2019-09-23 12:35:11 +02:00
else:
Log.error(self, "SSH config file not found")
2019-09-23 12:11:15 +02:00
2019-09-23 13:09:15 +02:00
@expose(hide=True)
def secure_ssh_port(self):
"""Change SSH port"""
WOGit.add(self, ["/etc/ssh"],
msg="Adding changed SSH port into Git")
pargs = self.app.pargs
if pargs.user_input:
while ((not pargs.user_input.isdigit()) and
2019-09-23 14:24:16 +02:00
(not pargs.user_input < 65536)):
2019-09-23 13:09:15 +02:00
Log.info(self, "Please enter a valid port number ")
pargs.user_input = input("Server "
"SSH port [22]:")
if not pargs.user_input:
port = input("Server SSH port [22]:")
if port == "":
port = 22
2019-09-23 14:24:16 +02:00
while (not port.isdigit()) and (port != "") and (not port < 65536):
2019-09-23 13:09:15 +02:00
Log.info(self, "Please Enter valid port number :")
port = input("Server SSH port [22]:")
pargs.user_input = port
2019-10-08 11:17:07 +02:00
if WOFileUtils.grepcheck(self, '/etc/ssh/sshd_config', '#Port'):
WOShellExec.cmd_exec(self, "sed -i \"s/#Port.*/Port "
"{port}/\" /etc/ssh/sshd_config"
.format(port=pargs.user_input))
else:
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
2019-09-23 13:09:15 +02:00
WOGit.add(self, ["/etc/ssh"],
msg="Adding changed SSH port into Git")
# restart ssh service
2019-09-23 13:09:15 +02:00
if not WOService.restart_service(self, 'ssh'):
Log.error(self, "service SSH restart failed.")
Log.info(self, "Successfully changed SSH port to {port}"
.format(port=pargs.user_input))
2018-11-13 21:55:59 +01:00
def load(app):
2019-09-24 00:01:20 +02:00
app.handler.register(WOSecureController)
2019-09-24 00:04:32 +02:00
app.hook.register('post_argument_parsing', wo_secure_hook)