Add session security headers

This commit is contained in:
Henri 2022-09-15 22:11:47 +02:00
parent 538ec90b1f
commit 5b3ea75164
3 changed files with 45 additions and 19 deletions

View File

@ -10,7 +10,7 @@ All notable changes to this project are documented in this file.
### Changed ### Changed
- n/a - Add session security headers
### Fixed ### Fixed

View File

@ -19,7 +19,7 @@ from . import setupenv_alpine
def is_root(): def is_root():
"""Returns whether this script is run with user is 0 (root)""" """Returns whether this script is run with user id 0 (root)"""
return os.getuid() == 0 return os.getuid() == 0
def get_user(): def get_user():
@ -108,6 +108,14 @@ def get_primary_interface_addr4():
else: else:
return None return None
def get_second_subnet():
"""Returns the second /28 subnet of the local network as well as the local network"""
ip = get_primary_interface_addr4()
net = ipaddress.ip_interface(ip).network
nets = net.subnets(new_prefix=28)
next(nets)
return next(nets), net
class QueryUser(): class QueryUser():
"""Interact with the user""" """Interact with the user"""
@ -116,8 +124,11 @@ class QueryUser():
"""Object initialization""" """Object initialization"""
self._expert = None self._expert = None
def input_yes_no(self, display_text, default='Yes'): def input_yes_no(self, display_text, default='Yes', expert_question=False):
"""Queries the user for a yes or no answer""" """Queries the user for a yes or no answer"""
if expert_question is not None:
if expert_question and not self.expert:
return default
while True: while True:
answer = input(f' {display_text} ') answer = input(f' {display_text} ')
answer = answer.strip() answer = answer.strip()
@ -132,7 +143,7 @@ class QueryUser():
def query_expert(self): def query_expert(self):
"""Queries the user on whether he wants expert configuration""" """Queries the user on whether he wants expert configuration"""
expert = self.input_yes_no('Do you want to use expert configuration? [No]:', default='No') expert = self.input_yes_no('Do you want to use expert configuration? [No]:', default='No', expert_question=None)
return expert return expert
@property @property
@ -244,7 +255,7 @@ class QueryUser():
print(' In a home environment, this is usually a DynDNS name denoting your Internet router.') print(' In a home environment, this is usually a DynDNS name denoting your Internet router.')
return self.get_and_validate_input('Please specify the endpoint hostname (and optionally port) to reach your WireGuard server:', default='', check_function=check, expert_question=False) return self.get_and_validate_input('Please specify the endpoint hostname (and optionally port) to reach your WireGuard server:', default='', check_function=check, expert_question=False)
def get_wg_address(self): def get_wg_address(self, default='192.168.0.17/28'):
"""Query the user for the IP address of the WireGuard interface incl. prefix length""" """Query the user for the IP address of the WireGuard interface incl. prefix length"""
def check(userdata): def check(userdata):
@ -255,7 +266,7 @@ class QueryUser():
print(' Exception: {text}'.format(text=str(e))) print(' Exception: {text}'.format(text=str(e)))
return None return None
return self.get_and_validate_input('Please specify the IP address of the WireGuard interface incl. prefix length [192.168.0.17/28]:', default='192.168.0.17/28', check_function=check, expert_question=False) return self.get_and_validate_input(f'Please specify the IP address of the WireGuard interface incl. prefix length [{default}]:', default=default, check_function=check, expert_question=False)
def get_wg_networks(self): def get_wg_networks(self):
"""Query the user for the network ranges that the clients shall route to the WireGuard server""" """Query the user for the network ranges that the clients shall route to the WireGuard server"""
@ -307,17 +318,21 @@ def setup_environment():
if os.path.exists(cfg.wg_configfile): if os.path.exists(cfg.wg_configfile):
print(f'WireGuard config file {cfg.wg_configfile} already exists. Ok.') print(f'WireGuard config file {cfg.wg_configfile} already exists. Ok.')
else: else:
network_subrange, network_local = get_second_subnet()
ip_wgif = network_subrange[1]
print(f'WireGuard config file {cfg.wg_configfile} does not yet exist. Let\'s create one...') print(f'WireGuard config file {cfg.wg_configfile} does not yet exist. Let\'s create one...')
print(' For documentation on possible setups, please refer to') print(' For documentation on possible setups, please refer to')
print(' https://github.com/towalink/wgfrontend/tree/main/doc/network-integration') print(' https://github.com/towalink/wgfrontend/tree/main/doc/network-integration')
print(' Automated configuration is only supported for the ProxyARP setup.') print(' Automated configuration is only supported for the ProxyARP setup.')
print(' For this, choose an unused subrange of your local network for WireGuard.') print(' For this, choose an unused subrange of your local network for WireGuard.')
print(' If your local network were 192.168.0.0/24, you could use the subrange') print(f' For your local network {network_local.exploded}, you could e.g. use the subrange')
print(' 192.168.0.16/28 with 192.168.0.17/28 as the WireGuard interface address.') print(f' {network_subrange.exploded} with {ip_wgif}/{network_subrange.prefixlen} as the WireGuard interface address')
print(' Press enter to select defaults.') print(f' and the other addresses for clients. For this, the addresses from {network_subrange.network_address}')
print(f' to {network_subrange.broadcast_address} must not be in use in your network.')
print(f' Press enter to select defaults.')
wg_listenport = qu.get_wg_listenport() wg_listenport = qu.get_wg_listenport()
endpoint = qu.get_endpoint() endpoint = qu.get_endpoint()
wg_address_obj = qu.get_wg_address() wg_address_obj = qu.get_wg_address(default=ip_wgif.exploded + '/' + str(network_subrange.prefixlen))
wg_networks = qu.get_wg_networks() wg_networks = qu.get_wg_networks()
# Check for ProxyARP setup # Check for ProxyARP setup
proxy_arp_interface = None proxy_arp_interface = None
@ -327,7 +342,7 @@ def setup_environment():
if wg_address_obj.network.subnet_of(eth_address_obj.network): if wg_address_obj.network.subnet_of(eth_address_obj.network):
interface_name = get_primary_interface() interface_name = get_primary_interface()
print(' Setup for ProxyARP detected.') print(' Setup for ProxyARP detected.')
if qu.input_yes_no(f'7e) Do you want to configure ProxyARP on interface {interface_name} when bringing up the WireGuard interface? [Yes]: '): if qu.input_yes_no(f'7e) Do you want to configure ProxyARP on interface {interface_name} when bringing up the WireGuard interface? [Yes]: ', expert_question=True):
proxy_arp_interface = interface_name proxy_arp_interface = interface_name
else: else:
print(' Please configure your network setup based on the documentation referenced above.') print(' Please configure your network setup based on the documentation referenced above.')
@ -344,7 +359,19 @@ def setup_environment():
wc.write_file() wc.write_file()
print(' Config file written. Ok.') print(' Config file written. Ok.')
eh = exechelper.ExecHelper() eh = exechelper.ExecHelper()
if qu.input_yes_no(f'Would you like to allow the system user of the web frontend to reload WireGuard on config on changes (using sudo)? [Yes]:'): if qu.input_yes_no(f'Would you like to enable IP Forwarding so that this device can act as a router? [Yes]:', expert_question=True):
eh.execute('sysctl -w net.ipv4.ip_forward=1', suppressoutput=True, suppresserrors=False)
eh.execute('sysctl -w net.ipv6.conf.all.forwarding=1', suppressoutput=True, suppresserrors=False)
ipforwarding_content = textwrap.dedent(f'''\
net.ipv4.ip_forward = 1
net.ipv6.conf.all.forwarding = 1
''')
if os.path.isdir('/etc/sysctl.d'):
with open('/etc/sysctl.d/99-wgfrontend-forwarding.conf', 'w') as ipforwarding_file:
ipforwarding_file.write(ipforwarding_content)
else:
print(' Sorry, "/etc/sysctl.d" does not exist so that we could not install a config file there.')
if qu.input_yes_no(f'Would you like to allow the system user of the web frontend to reload WireGuard on config changes (using sudo)? [Yes]:'):
sudoers_content = textwrap.dedent(f'''\ sudoers_content = textwrap.dedent(f'''\
{cfg.user} ALL=(root) NOPASSWD: /etc/init.d/wgfrontend_interface start, /etc/init.d/wgfrontend_interface stop, /etc/init.d/wgfrontend_interface restart {cfg.user} ALL=(root) NOPASSWD: /etc/init.d/wgfrontend_interface start, /etc/init.d/wgfrontend_interface stop, /etc/init.d/wgfrontend_interface restart
{cfg.user} ALL=(root) NOPASSWD: /usr/bin/wg-quick down {cfg.wg_configfile}, /usr/bin/wg-quick up {cfg.wg_configfile} {cfg.user} ALL=(root) NOPASSWD: /usr/bin/wg-quick down {cfg.wg_configfile}, /usr/bin/wg-quick up {cfg.wg_configfile}
@ -354,14 +381,14 @@ def setup_environment():
sudoers_file.write(sudoers_content) sudoers_file.write(sudoers_content)
else: else:
print(' Sorry, "/etc/sudoers.d" does not exist so that sudo could not be configured. Maybe "sudo" is not installed.') print(' Sorry, "/etc/sudoers.d" does not exist so that sudo could not be configured. Maybe "sudo" is not installed.')
if qu.input_yes_no(f'Would you like to activate the WireGuard interface "{cfg.wg_interface}" now? [Yes]:'): if qu.input_yes_no(f'Would you like to activate the WireGuard interface "{cfg.wg_interface}" now? [Yes]:', expert_question=True):
eh.run_wgquick('up', cfg.wg_interface) eh.run_wgquick('up', cfg.wg_interface)
if qu.input_yes_no(f'Would you like to activate the WireGuard interface "{cfg.wg_interface}" on boot? [Yes]:'): if qu.input_yes_no(f'Would you like to activate the WireGuard interface "{cfg.wg_interface}" on boot? [Yes]:', expert_question=True):
if eh.os_id == 'alpine': if eh.os_id == 'alpine':
setupenv_alpine.start_wginterface_onboot() setupenv_alpine.start_wginterface_onboot()
else: else:
eh.enable_service(f'wg-quick@{cfg.wg_interface}') eh.enable_service(f'wg-quick@{cfg.wg_interface}')
if qu.input_yes_no(f'Would you like to start wgfrontend on boot? [Yes]:'): if qu.input_yes_no(f'Would you like to start wgfrontend on boot? [Yes]:', expert_question=True):
if eh.os_id == 'alpine': if eh.os_id == 'alpine':
setupenv_alpine.start_wgfrontend_onboot() setupenv_alpine.start_wgfrontend_onboot()
else: else:
@ -379,15 +406,12 @@ def setup_environment():
Group={cfg.user} Group={cfg.user}
StandardOutput=append:/var/log/wgfrontend.log StandardOutput=append:/var/log/wgfrontend.log
StandardError=inherit StandardError=inherit
# journalctl -u sh-dimplex
#StandardOutput=syslog
#StandardError=syslog
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target
''') ''')
if os.path.isdir('/etc/systemd/system'): if os.path.isdir('/etc/systemd/system'):
with open('/etc/systemd/system/smarthome.service', 'w') as systemd_file: with open('/etc/systemd/system/wgfrontend.service', 'w') as systemd_file:
systemd_file.write(systemd_content) systemd_file.write(systemd_content)
eh.execute('systemctl daemon-reload', suppressoutput=True, suppresserrors=True) eh.execute('systemctl daemon-reload', suppressoutput=True, suppresserrors=True)
eh.enable_service('wgfrontend') eh.enable_service('wgfrontend')

View File

@ -112,6 +112,8 @@ def run_webapp(cfg):
}, },
'/': { '/': {
'tools.sessions.on': True, 'tools.sessions.on': True,
'tools.sessions.secure': True,
'tools.sessions.httponly': True,
'tools.staticdir.root': os.path.join(script_path, 'webroot'), 'tools.staticdir.root': os.path.join(script_path, 'webroot'),
'tools.session_auth.on': True, 'tools.session_auth.on': True,
'tools.session_auth.login_screen': app.login_screen, 'tools.session_auth.login_screen': app.login_screen,