fix: fix some security issues

This commit is contained in:
Evgenii Alekseev 2023-11-12 22:22:05 +02:00
parent 62dd77317d
commit e03fcbfab5
11 changed files with 47 additions and 33 deletions

View File

@ -2,7 +2,7 @@
_shtab_ahriman_subparsers=('aur-search' 'search' 'help-commands-unsafe' 'help' 'help-updates' 'help-version' 'version' 'package-add' 'add' 'package-update' 'package-remove' 'remove' 'package-status' 'status' 'package-status-remove' 'package-status-update' 'status-update' 'patch-add' 'patch-list' 'patch-remove' 'patch-set-add' 'repo-backup' 'repo-check' 'check' 'repo-create-keyring' 'repo-create-mirrorlist' 'repo-daemon' 'daemon' 'repo-rebuild' 'rebuild' 'repo-remove-unknown' 'remove-unknown' 'repo-report' 'report' 'repo-restore' 'repo-sign' 'sign' 'repo-status-update' 'repo-sync' 'sync' 'repo-tree' 'repo-triggers' 'repo-update' 'update' 'service-clean' 'clean' 'repo-clean' 'service-config' 'config' 'repo-config' 'service-config-validate' 'config-validate' 'repo-config-validate' 'service-key-import' 'key-import' 'service-repositories' 'service-run' 'run' 'service-setup' 'init' 'repo-init' 'repo-setup' 'setup' 'service-shell' 'shell' 'service-tree-migrate' 'user-add' 'user-list' 'user-remove' 'web') _shtab_ahriman_subparsers=('aur-search' 'search' 'help-commands-unsafe' 'help' 'help-updates' 'help-version' 'version' 'package-add' 'add' 'package-update' 'package-remove' 'remove' 'package-status' 'status' 'package-status-remove' 'package-status-update' 'status-update' 'patch-add' 'patch-list' 'patch-remove' 'patch-set-add' 'repo-backup' 'repo-check' 'check' 'repo-create-keyring' 'repo-create-mirrorlist' 'repo-daemon' 'daemon' 'repo-rebuild' 'rebuild' 'repo-remove-unknown' 'remove-unknown' 'repo-report' 'report' 'repo-restore' 'repo-sign' 'sign' 'repo-status-update' 'repo-sync' 'sync' 'repo-tree' 'repo-triggers' 'repo-update' 'update' 'service-clean' 'clean' 'repo-clean' 'service-config' 'config' 'repo-config' 'service-config-validate' 'config-validate' 'repo-config-validate' 'service-key-import' 'key-import' 'service-repositories' 'service-run' 'run' 'service-setup' 'init' 'repo-init' 'repo-setup' 'setup' 'service-shell' 'shell' 'service-tree-migrate' 'user-add' 'user-list' 'user-remove' 'web')
_shtab_ahriman_option_strings=('-h' '--help' '-a' '--architecture' '-c' '--configuration' '--force' '-l' '--lock' '--log-handler' '-q' '--quiet' '--report' '--no-report' '-r' '--repository' '--unsafe' '--wait-timeout' '-V' '--version') _shtab_ahriman_option_strings=('-h' '--help' '-a' '--architecture' '-c' '--configuration' '--force' '-l' '--lock' '--log-handler' '-q' '--quiet' '--report' '--no-report' '-r' '--repository' '--unsafe' '-V' '--version' '--wait-timeout')
_shtab_ahriman_aur_search_option_strings=('-h' '--help' '-e' '--exit-code' '--info' '--no-info' '--sort-by') _shtab_ahriman_aur_search_option_strings=('-h' '--help' '-e' '--exit-code' '--info' '--no-info' '--sort-by')
_shtab_ahriman_search_option_strings=('-h' '--help' '-e' '--exit-code' '--info' '--no-info' '--sort-by') _shtab_ahriman_search_option_strings=('-h' '--help' '-e' '--exit-code' '--info' '--no-info' '--sort-by')
_shtab_ahriman_help_commands_unsafe_option_strings=('-h' '--help') _shtab_ahriman_help_commands_unsafe_option_strings=('-h' '--help')

View File

@ -1,9 +1,9 @@
.TH AHRIMAN "1" "2023\-11\-06" "ahriman" "Generated Python Manual" .TH AHRIMAN "1" "2023\-11\-12" "ahriman" "Generated Python Manual"
.SH NAME .SH NAME
ahriman ahriman
.SH SYNOPSIS .SH SYNOPSIS
.B ahriman .B ahriman
[-h] [-a ARCHITECTURE] [-c CONFIGURATION] [--force] [-l LOCK] [--log-handler {console,syslog,journald}] [-q] [--report | --no-report] [-r REPOSITORY] [--unsafe] [--wait-timeout WAIT_TIMEOUT] [-V] {aur-search,search,help-commands-unsafe,help,help-updates,help-version,version,package-add,add,package-update,package-remove,remove,package-status,status,package-status-remove,package-status-update,status-update,patch-add,patch-list,patch-remove,patch-set-add,repo-backup,repo-check,check,repo-create-keyring,repo-create-mirrorlist,repo-daemon,daemon,repo-rebuild,rebuild,repo-remove-unknown,remove-unknown,repo-report,report,repo-restore,repo-sign,sign,repo-status-update,repo-sync,sync,repo-tree,repo-triggers,repo-update,update,service-clean,clean,repo-clean,service-config,config,repo-config,service-config-validate,config-validate,repo-config-validate,service-key-import,key-import,service-repositories,service-run,run,service-setup,init,repo-init,repo-setup,setup,service-shell,shell,service-tree-migrate,user-add,user-list,user-remove,web} ... [-h] [-a ARCHITECTURE] [-c CONFIGURATION] [--force] [-l LOCK] [--log-handler {console,syslog,journald}] [-q] [--report | --no-report] [-r REPOSITORY] [--unsafe] [-V] [--wait-timeout WAIT_TIMEOUT] {aur-search,search,help-commands-unsafe,help,help-updates,help-version,version,package-add,add,package-update,package-remove,remove,package-status,status,package-status-remove,package-status-update,status-update,patch-add,patch-list,patch-remove,patch-set-add,repo-backup,repo-check,check,repo-create-keyring,repo-create-mirrorlist,repo-daemon,daemon,repo-rebuild,rebuild,repo-remove-unknown,remove-unknown,repo-report,report,repo-restore,repo-sign,sign,repo-status-update,repo-sync,sync,repo-tree,repo-triggers,repo-update,update,service-clean,clean,repo-clean,service-config,config,repo-config,service-config-validate,config-validate,repo-config-validate,service-key-import,key-import,service-repositories,service-run,run,service-setup,init,repo-init,repo-setup,setup,service-shell,shell,service-tree-migrate,user-add,user-list,user-remove,web} ...
.SH DESCRIPTION .SH DESCRIPTION
ArcH linux ReposItory MANager ArcH linux ReposItory MANager
@ -44,15 +44,15 @@ filter by target repository
\fB\-\-unsafe\fR \fB\-\-unsafe\fR
allow to run ahriman as non\-ahriman user. Some actions might be unavailable allow to run ahriman as non\-ahriman user. Some actions might be unavailable
.TP
\fB\-V\fR, \fB\-\-version\fR
show program's version number and exit
.TP .TP
\fB\-\-wait\-timeout\fR \fI\,WAIT_TIMEOUT\/\fR \fB\-\-wait\-timeout\fR \fI\,WAIT_TIMEOUT\/\fR
wait for lock to be free. Negative value will lead to immediate application run even if there is lock file. In case of wait for lock to be free. Negative value will lead to immediate application run even if there is lock file. In case of
zero value, the application will wait infinitely zero value, the application will wait infinitely
.TP
\fB\-V\fR, \fB\-\-version\fR
show program's version number and exit
.SH .SH
COMMAND COMMAND
.TP .TP
@ -208,22 +208,22 @@ sort field by this field. In case if two packages have the same value of the spe
by name by name
.SH COMMAND \fI\,'ahriman help\-commands\-unsafe'\/\fR .SH COMMAND \fI\,'ahriman help\-commands\-unsafe'\/\fR
usage: ahriman help\-commands\-unsafe [\-h] [command ...] usage: ahriman help\-commands\-unsafe [\-h] [subcommand ...]
list unsafe commands as defined in default args list unsafe commands as defined in default args
.TP .TP
\fBcommand\fR \fBsubcommand\fR
instead of showing commands, just test command line for unsafe subcommand and return 0 in case if command is safe and 1 instead of showing commands, just test command line for unsafe subcommand and return 0 in case if command is safe and 1
otherwise otherwise
.SH COMMAND \fI\,'ahriman help'\/\fR .SH COMMAND \fI\,'ahriman help'\/\fR
usage: ahriman help [\-h] [command] usage: ahriman help [\-h] [subcommand]
show help message for application or command and exit show help message for application or command and exit
.TP .TP
\fBcommand\fR \fBsubcommand\fR
show help message for specific command show help message for specific command
.SH COMMAND \fI\,'ahriman help\-updates'\/\fR .SH COMMAND \fI\,'ahriman help\-updates'\/\fR

View File

@ -90,8 +90,8 @@ _shtab_ahriman_options=(
{--report,--no-report}"[force enable or disable reporting to web service (default\: True)]:report:" {--report,--no-report}"[force enable or disable reporting to web service (default\: True)]:report:"
{-r,--repository}"[filter by target repository (default\: None)]:repository:" {-r,--repository}"[filter by target repository (default\: None)]:repository:"
"--unsafe[allow to run ahriman as non-ahriman user. Some actions might be unavailable (default\: False)]" "--unsafe[allow to run ahriman as non-ahriman user. Some actions might be unavailable (default\: False)]"
"--wait-timeout[wait for lock to be free. Negative value will lead to immediate application run even if there is lock file. In case of zero value, the application will wait infinitely (default\: -1)]:wait_timeout:"
"(- : *)"{-V,--version}"[show program\'s version number and exit]" "(- : *)"{-V,--version}"[show program\'s version number and exit]"
"--wait-timeout[wait for lock to be free. Negative value will lead to immediate application run even if there is lock file. In case of zero value, the application will wait infinitely (default\: -1)]:wait_timeout:"
) )
_shtab_ahriman_add_options=( _shtab_ahriman_add_options=(

View File

@ -87,11 +87,11 @@ def _parser() -> argparse.ArgumentParser:
parser.add_argument("--repository-id", help=argparse.SUPPRESS) parser.add_argument("--repository-id", help=argparse.SUPPRESS)
parser.add_argument("--unsafe", help="allow to run ahriman as non-ahriman user. Some actions might be unavailable", parser.add_argument("--unsafe", help="allow to run ahriman as non-ahriman user. Some actions might be unavailable",
action="store_true") action="store_true")
parser.add_argument("-V", "--version", action="version", version=__version__)
parser.add_argument("--wait-timeout", help="wait for lock to be free. Negative value will lead to " parser.add_argument("--wait-timeout", help="wait for lock to be free. Negative value will lead to "
"immediate application run even if there is lock file. " "immediate application run even if there is lock file. "
"In case of zero value, the application will wait infinitely", "In case of zero value, the application will wait infinitely",
type=int, default=-1) type=int, default=-1)
parser.add_argument("-V", "--version", action="version", version=__version__)
subparsers = parser.add_subparsers(title="command", help="command to run", dest="command") subparsers = parser.add_subparsers(title="command", help="command to run", dest="command")
@ -178,7 +178,7 @@ def _set_help_commands_unsafe_parser(root: SubParserAction) -> argparse.Argument
""" """
parser = root.add_parser("help-commands-unsafe", help="list unsafe commands", parser = root.add_parser("help-commands-unsafe", help="list unsafe commands",
description="list unsafe commands as defined in default args", formatter_class=_formatter) description="list unsafe commands as defined in default args", formatter_class=_formatter)
parser.add_argument("command", help="instead of showing commands, just test command line for unsafe subcommand " parser.add_argument("subcommand", help="instead of showing commands, just test command line for unsafe subcommand "
"and return 0 in case if command is safe and 1 otherwise", nargs="*") "and return 0 in case if command is safe and 1 otherwise", nargs="*")
parser.set_defaults(handler=handlers.UnsafeCommands, architecture="", lock=None, quiet=True, report=False, parser.set_defaults(handler=handlers.UnsafeCommands, architecture="", lock=None, quiet=True, report=False,
repository="", unsafe=True, parser=_parser) repository="", unsafe=True, parser=_parser)
@ -198,7 +198,7 @@ def _set_help_parser(root: SubParserAction) -> argparse.ArgumentParser:
parser = root.add_parser("help", help="show help message", parser = root.add_parser("help", help="show help message",
description="show help message for application or command and exit", description="show help message for application or command and exit",
formatter_class=_formatter) formatter_class=_formatter)
parser.add_argument("command", help="show help message for specific command", nargs="?") parser.add_argument("subcommand", help="show help message for specific command", nargs="?")
parser.set_defaults(handler=handlers.Help, architecture="", lock=None, quiet=True, report=False, repository="", parser.set_defaults(handler=handlers.Help, architecture="", lock=None, quiet=True, report=False, repository="",
unsafe=True, parser=_parser) unsafe=True, parser=_parser)
return parser return parser

View File

@ -44,7 +44,7 @@ class Help(Handler):
report(bool): force enable or disable reporting report(bool): force enable or disable reporting
""" """
parser: argparse.ArgumentParser = args.parser() parser: argparse.ArgumentParser = args.parser()
if args.command is None: if args.subcommand is None:
parser.parse_args(["--help"]) parser.parse_args(["--help"])
else: else:
parser.parse_args([args.command, "--help"]) parser.parse_args([args.subcommand, "--help"])

View File

@ -46,8 +46,8 @@ class UnsafeCommands(Handler):
""" """
parser = args.parser() parser = args.parser()
unsafe_commands = UnsafeCommands.get_unsafe_commands(parser) unsafe_commands = UnsafeCommands.get_unsafe_commands(parser)
if args.command: if args.subcommand:
UnsafeCommands.check_unsafe(args.command, unsafe_commands, parser) UnsafeCommands.check_unsafe(args.subcommand, unsafe_commands, parser)
else: else:
for command in unsafe_commands: for command in unsafe_commands:
StringPrinter(command)(verbose=True) StringPrinter(command)(verbose=True)

View File

@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from aiohttp.web import HTTPFound from aiohttp.web import HTTPFound, HTTPNotFound
from ahriman.models.user_access import UserAccess from ahriman.models.user_access import UserAccess
from ahriman.web.views.base import BaseView from ahriman.web.views.base import BaseView
@ -40,5 +40,8 @@ class StaticView(BaseView):
Raises: Raises:
HTTPFound: on success response HTTPFound: on success response
HTTPNotFound: if path is invalid or unknown
""" """
if self.request.path in self.ROUTES: # explicit validation
raise HTTPFound(f"/static{self.request.path}") raise HTTPFound(f"/static{self.request.path}")
raise HTTPNotFound

View File

@ -18,7 +18,7 @@ def _default_args(args: argparse.Namespace) -> argparse.Namespace:
argparse.Namespace: generated arguments for these test cases argparse.Namespace: generated arguments for these test cases
""" """
args.parser = _parser args.parser = _parser
args.command = None args.subcommand = None
return args return args
@ -39,7 +39,7 @@ def test_run_command(args: argparse.Namespace, configuration: Configuration, moc
must run command for specific subcommand must run command for specific subcommand
""" """
args = _default_args(args) args = _default_args(args)
args.command = "aur-search" args.subcommand = "aur-search"
parse_mock = mocker.patch("argparse.ArgumentParser.parse_args") parse_mock = mocker.patch("argparse.ArgumentParser.parse_args")
_, repository_id = configuration.check_loaded() _, repository_id = configuration.check_loaded()

View File

@ -19,7 +19,7 @@ def _default_args(args: argparse.Namespace) -> argparse.Namespace:
argparse.Namespace: generated arguments for these test cases argparse.Namespace: generated arguments for these test cases
""" """
args.parser = _parser args.parser = _parser
args.command = [] args.subcommand = []
return args return args
@ -43,7 +43,7 @@ def test_run_check(args: argparse.Namespace, configuration: Configuration, mocke
must run command and check if command is unsafe must run command and check if command is unsafe
""" """
args = _default_args(args) args = _default_args(args)
args.command = ["clean"] args.subcommand = ["clean"]
commands_mock = mocker.patch("ahriman.application.handlers.UnsafeCommands.get_unsafe_commands", commands_mock = mocker.patch("ahriman.application.handlers.UnsafeCommands.get_unsafe_commands",
return_value=["command"]) return_value=["command"])
check_mock = mocker.patch("ahriman.application.handlers.UnsafeCommands.check_unsafe") check_mock = mocker.patch("ahriman.application.handlers.UnsafeCommands.check_unsafe")

View File

@ -88,7 +88,7 @@ def test_clear(lock: Lock) -> None:
""" """
must remove lock file must remove lock file
""" """
lock.path = Path(tempfile.mktemp()) # nosec lock.path = Path(tempfile.gettempdir()) / "ahriman-test.lock"
lock.path.touch() lock.path.touch()
lock.clear() lock.clear()
@ -99,7 +99,7 @@ def test_clear_missing(lock: Lock) -> None:
""" """
must not fail on lock removal if file is missing must not fail on lock removal if file is missing
""" """
lock.path = Path(tempfile.mktemp()) # nosec lock.path = Path(tempfile.gettempdir()) / "ahriman-test.lock"
lock.clear() lock.clear()
@ -116,7 +116,7 @@ def test_create(lock: Lock) -> None:
""" """
must create lock must create lock
""" """
lock.path = Path(tempfile.mktemp()) # nosec lock.path = Path(tempfile.gettempdir()) / "ahriman-test.lock"
lock.create() lock.create()
assert lock.path.is_file() assert lock.path.is_file()
@ -127,7 +127,7 @@ def test_create_exception(lock: Lock) -> None:
""" """
must raise exception if file already exists must raise exception if file already exists
""" """
lock.path = Path(tempfile.mktemp()) # nosec lock.path = Path(tempfile.gettempdir()) / "ahriman-test.lock"
lock.path.touch() lock.path.touch()
with pytest.raises(DuplicateRunError): with pytest.raises(DuplicateRunError):
@ -149,7 +149,7 @@ def test_create_unsafe(lock: Lock) -> None:
must not raise exception if force flag set must not raise exception if force flag set
""" """
lock.force = True lock.force = True
lock.path = Path(tempfile.mktemp()) # nosec lock.path = Path(tempfile.gettempdir()) / "ahriman-test.lock"
lock.path.touch() lock.path.touch()
lock.create() lock.create()
@ -161,7 +161,7 @@ def test_watch(lock: Lock, mocker: MockerFixture) -> None:
must check if lock file exists must check if lock file exists
""" """
wait_mock = mocker.patch("ahriman.models.waiter.Waiter.wait") wait_mock = mocker.patch("ahriman.models.waiter.Waiter.wait")
lock.path = Path(tempfile.mktemp()) # nosec lock.path = Path(tempfile.gettempdir()) / "ahriman-test.lock"
lock.watch() lock.watch()
wait_mock.assert_called_once_with(lock.path.is_file) wait_mock.assert_called_once_with(lock.path.is_file)

View File

@ -24,8 +24,19 @@ def test_routes() -> None:
async def test_get(client_with_auth: TestClient) -> None: async def test_get(client_with_auth: TestClient) -> None:
""" """
must generate status page correctly (/) must redirect favicon to static files
""" """
response = await client_with_auth.get("/favicon.ico", allow_redirects=False) response = await client_with_auth.get("/favicon.ico", allow_redirects=False)
assert response.status == 302 assert response.status == 302
assert response.headers["Location"] == "/static/favicon.ico" assert response.headers["Location"] == "/static/favicon.ico"
async def test_get_not_found(client_with_auth: TestClient) -> None:
"""
must raise not found if path is invalid
"""
for route in client_with_auth.app.router.routes():
if hasattr(route.handler, "ROUTES"):
route.handler.ROUTES = []
response = await client_with_auth.get("/favicon.ico", allow_redirects=False)
assert response.status == 404