mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-08-26 12:46:08 +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
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
"""
|
"""FreedomBox app for security configuration."""
|
||||||
FreedomBox app for security configuration.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
@ -16,13 +14,7 @@ from plinth.daemon import Daemon, RelatedDaemon
|
|||||||
from plinth.modules.backups.components import BackupRestore
|
from plinth.modules.backups.components import BackupRestore
|
||||||
from plinth.package import Packages
|
from plinth.package import Packages
|
||||||
|
|
||||||
from . import manifest
|
from . import manifest, 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]
|
|
||||||
|
|
||||||
|
|
||||||
class SecurityApp(app_module.App):
|
class SecurityApp(app_module.App):
|
||||||
@ -66,43 +58,28 @@ class SecurityApp(app_module.App):
|
|||||||
actions.superuser_run('service', ['reload', 'fail2ban'])
|
actions.superuser_run('service', ['reload', 'fail2ban'])
|
||||||
|
|
||||||
# Migrate to new config file.
|
# Migrate to new config file.
|
||||||
enabled = get_restricted_access_enabled()
|
enabled = privileged.get_restricted_access_enabled()
|
||||||
set_restricted_access(False)
|
set_restricted_access(False)
|
||||||
if enabled:
|
if enabled:
|
||||||
set_restricted_access(True)
|
set_restricted_access(True)
|
||||||
|
|
||||||
|
|
||||||
def enable_fail2ban():
|
def enable_fail2ban():
|
||||||
|
"""Unmask, enable and run the fail2ban service."""
|
||||||
actions.superuser_run('service', ['unmask', 'fail2ban'])
|
actions.superuser_run('service', ['unmask', 'fail2ban'])
|
||||||
actions.superuser_run('service', ['enable', '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):
|
def set_restricted_access(enabled):
|
||||||
"""Enable or disable restricted access"""
|
"""Enable or disable restricted access."""
|
||||||
action = 'disable-restricted-access'
|
|
||||||
if enabled:
|
if enabled:
|
||||||
action = 'enable-restricted-access'
|
privileged.enable_restricted_access()
|
||||||
|
else:
|
||||||
actions.superuser_run('security', [action])
|
privileged.disable_restricted_access()
|
||||||
|
|
||||||
|
|
||||||
def get_apps_report():
|
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')
|
lines = subprocess.check_output(['debsecan']).decode().split('\n')
|
||||||
cves = defaultdict(set)
|
cves = defaultdict(set)
|
||||||
for line in lines:
|
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
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
"""
|
"""Views for security module."""
|
||||||
Views for security module
|
|
||||||
"""
|
|
||||||
|
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.template.response import TemplateResponse
|
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.modules.upgrades import is_backports_requested
|
||||||
from plinth.views import AppView
|
from plinth.views import AppView
|
||||||
|
|
||||||
|
from . import privileged
|
||||||
from .forms import SecurityForm
|
from .forms import SecurityForm
|
||||||
|
|
||||||
|
|
||||||
@ -42,15 +41,15 @@ class SecurityAppView(AppView):
|
|||||||
|
|
||||||
|
|
||||||
def get_status(request):
|
def get_status(request):
|
||||||
"""Return the current status"""
|
"""Return the current status."""
|
||||||
return {
|
return {
|
||||||
'restricted_access': security.get_restricted_access_enabled(),
|
'restricted_access': privileged.get_restricted_access_enabled(),
|
||||||
'fail2ban_enabled': action_utils.service_is_enabled('fail2ban')
|
'fail2ban_enabled': action_utils.service_is_enabled('fail2ban')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def _apply_changes(request, old_status, new_status):
|
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']:
|
if old_status['restricted_access'] != new_status['restricted_access']:
|
||||||
try:
|
try:
|
||||||
security.set_restricted_access(new_status['restricted_access'])
|
security.set_restricted_access(new_status['restricted_access'])
|
||||||
@ -70,7 +69,7 @@ def _apply_changes(request, old_status, new_status):
|
|||||||
|
|
||||||
|
|
||||||
def report(request):
|
def report(request):
|
||||||
"""Serve the security report page"""
|
"""Serve the security report page."""
|
||||||
apps_report = security.get_apps_report()
|
apps_report = security.get_apps_report()
|
||||||
return TemplateResponse(
|
return TemplateResponse(
|
||||||
request, 'security_report.html', {
|
request, 'security_report.html', {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user