review unsafe commands access

Some commands were made unsafe in old versions, but nowadays they can be
run without having special privileges.

There was also a bug in which status commands were not available if you
are not ahriman user and unix socket is used. It has been fixed by
switching to manual socket creation (see also
https://github.com/aio-libs/aiohttp/issues/4155)
This commit is contained in:
2023-01-04 16:48:18 +02:00
parent ab650b7417
commit ef6cf0f00b
8 changed files with 115 additions and 22 deletions

View File

@ -399,7 +399,8 @@ def _set_patch_list_parser(root: SubParserAction) -> argparse.ArgumentParser:
parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true")
parser.add_argument("-v", "--variable", help="if set, show only patches for specified PKGBUILD variables",
action="append")
parser.set_defaults(handler=handlers.Patch, action=Action.List, architecture=[""], lock=None, report=False)
parser.set_defaults(handler=handlers.Patch, action=Action.List, architecture=[""], lock=None, report=False,
unsafe=True)
return parser
@ -718,7 +719,7 @@ def _set_repo_tree_parser(root: SubParserAction) -> argparse.ArgumentParser:
parser = root.add_parser("repo-tree", help="dump repository tree",
description="dump repository tree based on packages dependencies",
formatter_class=_formatter)
parser.set_defaults(handler=handlers.Structure, lock=None, report=False, quiet=True)
parser.set_defaults(handler=handlers.Structure, lock=None, report=False, quiet=True, unsafe=True)
return parser
@ -814,7 +815,7 @@ def _set_user_add_parser(root: SubParserAction) -> argparse.ArgumentParser:
type=UserAccess, choices=enum_values(UserAccess), default=UserAccess.Read)
parser.add_argument("-s", "--secure", help="set file permissions to user-only", action="store_true")
parser.set_defaults(handler=handlers.Users, action=Action.Update, architecture=[""], lock=None, report=False,
quiet=True, unsafe=True)
quiet=True)
return parser
@ -854,7 +855,7 @@ def _set_user_remove_parser(root: SubParserAction) -> argparse.ArgumentParser:
formatter_class=_formatter)
parser.add_argument("username", help="username for web service")
parser.set_defaults(handler=handlers.Users, action=Action.Remove, architecture=[""], lock=None, report=False, # nosec
password="", quiet=True, unsafe=True)
password="", quiet=True)
return parser

View File

@ -160,7 +160,9 @@ class WebClient(Client, LazyLogging):
Returns:
str: full url of web service for specific package base
"""
return f"{self.address}/api/v1/packages/{package_base}"
# in case if unix socket is used we need to normalize url
suffix = f"/{package_base}" if package_base else ""
return f"{self.address}/api/v1/packages{suffix}"
def add(self, package: Package, status: BuildStatusEnum) -> None:
"""

View File

@ -20,8 +20,10 @@
import aiohttp_jinja2
import jinja2
import logging
import socket
from aiohttp import web
from typing import Optional
from ahriman.core.auth import Auth
from ahriman.core.configuration import Configuration
@ -34,7 +36,40 @@ from ahriman.web.middlewares.exception_handler import exception_handler
from ahriman.web.routes import setup_routes
__all__ = ["on_shutdown", "on_startup", "run_server", "setup_service"]
__all__ = ["create_socket", "on_shutdown", "on_startup", "run_server", "setup_service"]
def create_socket(configuration: Configuration, application: web.Application) -> Optional[socket.socket]:
"""
create unix socket based on configuration option
Args:
configuration(Configuration): configuration instance
application(web.Application): web application instance
Returns:
Optional[socket.socket]: unix socket object if set by option
"""
unix_socket = configuration.getpath("web", "unix_socket", fallback=None)
if unix_socket is None:
return None # no option set
# create unix socket and bind it
unix_socket.unlink(missing_ok=True) # remove socket file if it wasn't removed somehow before
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
sock.bind(str(unix_socket))
# allow everyone to write to the socket, otherwise API methods are not allowed by non-ahriman user
# by default sockets are created with same rights as directory is, but it seems that x bit is not really required
# see also https://github.com/aio-libs/aiohttp/issues/4155
if configuration.getboolean("web", "unix_socket_unsafe", fallback=True):
unix_socket.chmod(0o666) # for the glory of satan of course
# register socket removal
async def remove_socket(_: web.Application) -> None:
unix_socket.unlink(missing_ok=True)
application.on_shutdown.append(remove_socket)
return sock
async def on_shutdown(application: web.Application) -> None:
@ -78,9 +113,9 @@ def run_server(application: web.Application) -> None:
configuration: Configuration = application["configuration"]
host = configuration.get("web", "host")
port = configuration.getint("web", "port")
unix_socket = configuration.get("web", "unix_socket", fallback=None)
unix_socket = create_socket(configuration, application)
web.run_app(application, host=host, port=port, path=unix_socket, handle_signals=False,
web.run_app(application, host=host, port=port, sock=unix_socket, handle_signals=False,
access_log=logging.getLogger("http"), access_log_class=FilteredAccessLogger)