mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-01-28 08:03:36 +00:00
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:
parent
1e30b4c8fc
commit
97706cef8e
@ -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()
|
||||
@ -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:
|
||||
|
||||
56
plinth/modules/security/privileged.py
Normal file
56
plinth/modules/security/privileged.py
Normal 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
|
||||
@ -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', {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user