security: Use privileged decorator for actions

Tests:

- Functional tests work
- Initial setup during first setup works
  - Restricted access is enabled
- Enabling/disabling restricted access works. Configuration file is updated and
  app page shows correct value.

Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org>
Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
This commit is contained in:
Sunil Mohan Adapa 2022-08-26 10:23:47 -07:00 committed by James Valleroy
parent 1e30b4c8fc
commit 97706cef8e
No known key found for this signature in database
GPG Key ID: 77C0C75E7B650808
4 changed files with 71 additions and 106 deletions

View File

@ -1,67 +0,0 @@
#!/usr/bin/python3
# SPDX-License-Identifier: AGPL-3.0-or-later
"""
Helper for security configuration
"""
import argparse
import os
from plinth.modules.security import (ACCESS_CONF_FILE, ACCESS_CONF_FILE_OLD,
ACCESS_CONF_SNIPPET, ACCESS_CONF_SNIPPETS)
def parse_arguments():
"""Return parsed command line arguments as dictionary"""
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='subcommand', help='Sub command')
subparsers.add_parser(
'enable-restricted-access',
help='Restrict console login to users in admin or sudo group')
subparsers.add_parser(
'disable-restricted-access',
help='Don\'t restrict console login to users in admin or sudo group')
subparsers.required = True
return parser.parse_args()
def subcommand_enable_restricted_access(_):
"""Restrict console login to users in admin or sudo group."""
try:
os.mkdir(os.path.dirname(ACCESS_CONF_FILE))
except FileExistsError:
pass
with open(ACCESS_CONF_FILE, 'w', encoding='utf-8') as conffile:
conffile.write(ACCESS_CONF_SNIPPET + '\n')
def subcommand_disable_restricted_access(_):
"""Don't restrict console login to users in admin or sudo group."""
with open(ACCESS_CONF_FILE_OLD, 'r', encoding='utf-8') as conffile:
lines = conffile.readlines()
with open(ACCESS_CONF_FILE_OLD, 'w', encoding='utf-8') as conffile:
for line in lines:
if line.strip() not in ACCESS_CONF_SNIPPETS:
conffile.write(line)
try:
os.remove(ACCESS_CONF_FILE)
except OSError:
pass
def main():
"""Parse arguments and perform all duties"""
arguments = parse_arguments()
subcommand = arguments.subcommand.replace('-', '_')
subcommand_method = globals()['subcommand_' + subcommand]
subcommand_method(arguments)
if __name__ == '__main__':
main()

View File

@ -1,7 +1,5 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
"""
FreedomBox app for security configuration.
"""
"""FreedomBox app for security configuration."""
import re
import subprocess
@ -16,13 +14,7 @@ from plinth.daemon import Daemon, RelatedDaemon
from plinth.modules.backups.components import BackupRestore
from plinth.package import Packages
from . import manifest
ACCESS_CONF_FILE = '/etc/security/access.d/50freedombox.conf'
ACCESS_CONF_FILE_OLD = '/etc/security/access.conf'
ACCESS_CONF_SNIPPET = '-:ALL EXCEPT root fbx plinth (admin) (sudo):ALL'
OLD_ACCESS_CONF_SNIPPET = '-:ALL EXCEPT root fbx (admin) (sudo):ALL'
ACCESS_CONF_SNIPPETS = [OLD_ACCESS_CONF_SNIPPET, ACCESS_CONF_SNIPPET]
from . import manifest, privileged
class SecurityApp(app_module.App):
@ -66,43 +58,28 @@ class SecurityApp(app_module.App):
actions.superuser_run('service', ['reload', 'fail2ban'])
# Migrate to new config file.
enabled = get_restricted_access_enabled()
enabled = privileged.get_restricted_access_enabled()
set_restricted_access(False)
if enabled:
set_restricted_access(True)
def enable_fail2ban():
"""Unmask, enable and run the fail2ban service."""
actions.superuser_run('service', ['unmask', 'fail2ban'])
actions.superuser_run('service', ['enable', 'fail2ban'])
def get_restricted_access_enabled():
"""Return whether restricted access is enabled"""
with open(ACCESS_CONF_FILE_OLD, 'r', encoding='utf-8') as conffile:
if any(line.strip() in ACCESS_CONF_SNIPPETS
for line in conffile.readlines()):
return True
try:
with open(ACCESS_CONF_FILE, 'r', encoding='utf-8') as conffile:
return any(line.strip() in ACCESS_CONF_SNIPPETS
for line in conffile.readlines())
except FileNotFoundError:
return False
def set_restricted_access(enabled):
"""Enable or disable restricted access"""
action = 'disable-restricted-access'
"""Enable or disable restricted access."""
if enabled:
action = 'enable-restricted-access'
actions.superuser_run('security', [action])
privileged.enable_restricted_access()
else:
privileged.disable_restricted_access()
def get_apps_report():
"""Return a security report for each app"""
"""Return a security report for each app."""
lines = subprocess.check_output(['debsecan']).decode().split('\n')
cves = defaultdict(set)
for line in lines:

View File

@ -0,0 +1,56 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
"""Helper for security configuration."""
import os
from plinth.actions import privileged
ACCESS_CONF_FILE = '/etc/security/access.d/50freedombox.conf'
ACCESS_CONF_FILE_OLD = '/etc/security/access.conf'
ACCESS_CONF_SNIPPET = '-:ALL EXCEPT root fbx plinth (admin) (sudo):ALL'
OLD_ACCESS_CONF_SNIPPET = '-:ALL EXCEPT root fbx (admin) (sudo):ALL'
ACCESS_CONF_SNIPPETS = [OLD_ACCESS_CONF_SNIPPET, ACCESS_CONF_SNIPPET]
@privileged
def enable_restricted_access():
"""Restrict console login to users in admin or sudo group."""
try:
os.mkdir(os.path.dirname(ACCESS_CONF_FILE))
except FileExistsError:
pass
with open(ACCESS_CONF_FILE, 'w', encoding='utf-8') as conffile:
conffile.write(ACCESS_CONF_SNIPPET + '\n')
@privileged
def disable_restricted_access():
"""Don't restrict console login to users in admin or sudo group."""
with open(ACCESS_CONF_FILE_OLD, 'r', encoding='utf-8') as conffile:
lines = conffile.readlines()
with open(ACCESS_CONF_FILE_OLD, 'w', encoding='utf-8') as conffile:
for line in lines:
if line.strip() not in ACCESS_CONF_SNIPPETS:
conffile.write(line)
try:
os.remove(ACCESS_CONF_FILE)
except OSError:
pass
def get_restricted_access_enabled():
"""Return whether restricted access is enabled."""
with open(ACCESS_CONF_FILE_OLD, 'r', encoding='utf-8') as conffile:
if any(line.strip() in ACCESS_CONF_SNIPPETS
for line in conffile.readlines()):
return True
try:
with open(ACCESS_CONF_FILE, 'r', encoding='utf-8') as conffile:
return any(line.strip() in ACCESS_CONF_SNIPPETS
for line in conffile.readlines())
except FileNotFoundError:
return False

View File

@ -1,7 +1,5 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
"""
Views for security module
"""
"""Views for security module."""
from django.contrib import messages
from django.template.response import TemplateResponse
@ -12,6 +10,7 @@ from plinth.modules import security
from plinth.modules.upgrades import is_backports_requested
from plinth.views import AppView
from . import privileged
from .forms import SecurityForm
@ -42,15 +41,15 @@ class SecurityAppView(AppView):
def get_status(request):
"""Return the current status"""
"""Return the current status."""
return {
'restricted_access': security.get_restricted_access_enabled(),
'restricted_access': privileged.get_restricted_access_enabled(),
'fail2ban_enabled': action_utils.service_is_enabled('fail2ban')
}
def _apply_changes(request, old_status, new_status):
"""Apply the form changes"""
"""Apply the form changes."""
if old_status['restricted_access'] != new_status['restricted_access']:
try:
security.set_restricted_access(new_status['restricted_access'])
@ -70,7 +69,7 @@ def _apply_changes(request, old_status, new_status):
def report(request):
"""Serve the security report page"""
"""Serve the security report page."""
apps_report = security.get_apps_report()
return TemplateResponse(
request, 'security_report.html', {