UNIX socket permission bits
I want to use a unix socket, but it's impossible when umask is 0022 or more strict, because the nginx www-data user can't connect and prints "Permission denied" error. I want to change the permission bits for the unix socket without touching umask (a strict umask is needed for other things of my project).
There was a PR https://github.com/aio-libs/aiohttp/pull/4002, but it's closed.
I tried to chmod after on_startup signal:
class MyApp:
def __init__(self, config):
self.config = config
self.webapp = web.Application()
self.webapp.on_startup.append(self._on_startup)
# ...
def run_app(self):
web.run_app(
self.webapp,
path=self.config.unix_socket,
# ...
)
async def _on_startup(self, webapp):
os.chmod(self.config.unix_socket, self.config.unix_socket_mode)
But the on_startup signal is called before creating the socket, and os.chmod raises "No such file or directory: 'webapp.sock'" error.
Please implement an option for this or explain how to do it with the current version of aiohttp
Or maybe add on_listen signal? :)
Changing the default unix socket permission is a bad idea because it opens a security hole.
Please note: you can create UNIX socket file before starting listening on it:
>>> import socket
>>> s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
>>> s.bind('socket.sock')
>>> s.close()
>>> import os
>>> os.chmod('socket.sock', 0o777)
>>> aiohttp.run(...)
Normally, you'd want to make the privileges stricter, not more open...
@asvetlov should we implement support for the socket privilege adjustment on the framework level?
@andreymal can't you just create a shared group for this purpose?
@webknjaz
can't you just create a shared group for this purpose?
It's exactly what I want! But the shared group will not work without correct permission bits for the socket (at least chmod g+w is required), and that's why this issue exists.
@asvetlov
you can create UNIX socket file before starting listening on it:
Unfortunately it does not work, because asyncio create_unix_server does os.remove automatically:
$ ls -l aiohttp.sock
srwxrwx--- 1 andreymal http 0 окт 8 21:03 aiohttp.sock
$ python -c 'from aiohttp import web; web.run_app(web.Application(), path="aiohttp.sock")'
======== Running on http://unix:aiohttp.sock: ========
(Press CTRL+C to quit)
^C
$ ls -l aiohttp.sock
srwx------ 1 andreymal andreymal 0 окт 8 21:10 aiohttp.sock
Actually this code works (based on aiohttp create_unix_server):
path = 'aiohttp.sock'
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
try:
if stat.S_ISSOCK(os.stat(path).st_mode):
os.remove(path)
except FileNotFoundError:
pass
except OSError as err:
logger.error('Unable to check or remove stale UNIX socket '
'%r: %r', path, err)
try:
sock.bind(path)
except OSError as exc:
sock.close()
if exc.errno == errno.EADDRINUSE:
msg = f'Address {path!r} is already in use'
raise OSError(errno.EADDRINUSE, msg) from None
else:
raise
except:
sock.close()
raise
os.chmod(path, 0o770)
web.run_app(app, sock=sock)
But I think this is too complicated :)
@andreymal sorry, I didn't check my version before posting.
sock snippet looks overcomplicated, agree.
I'm open for proposals, especially if they don't blow up a list of accepted arguments too much.
on_listen is also an interesting option. What is the full signature?
on_listen(app) is not enough.
on_listen(app, site) maybe, called several times for every listened web.BaseSite?
I'm trying to make aiohttp work in a Plesk environment but I'm facing this exact same issue.
What I'm doing right to make it work is:
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
os.unlink(SOCKET_NAME)
s.bind(SOCKET_NAME)
os.chmod(SOCKET_NAME, 0o777)
web.run_app(app, sock=s)
is it the right way?
@vitto32 that should work, I guess. But the privileges seem too broad. Though it's up to you and depends on your needs.
@webknjaz yep that works. I know the privileges are broad, but nginx and vhost users have different groups (in plesk).
666 works on my machine with nginx! I think it would be great if run_app did this automatically.
I stumbled into this too. I don't think it's safe if aiohttp would always set the permissions to 666 - we don't know how people are already using aiohttp. I do think it makes sense for run_app to have a way to set Unix socket permissions. Ideally something like this:
run_app(app, path="/tmp/app.socket", perms=0o666)
I have prepared code that does this, and I'd be happy to share it, but there are many caveats that need discussing - not only pertaining to my code, but also to Unix sockets in general. Should they be discussed here, or should I open a PR for this?
Hmm, the longer I look at this, the more difficult it gets. asyncio's loop.create_unix_socket doesn't support permissions, but this is the only place where permissions can be set without creating races, and without code duplication.
chmod(args.socket, 0o777)
after
aiorunner = web.AppRunner(app)
loop.run_until_complete(aiorunner.setup())
aiosite = web.UnixSite(aiorunner, args.socket)
loop.run_until_complete(aiosite.start())
solved the issue for me.
But I would prefer the better solution of course.
I'll have to take a look at this some time. Recently, I've just been using a systemd hack:
ExecStartPost=sleep 0.8
ExecStartPost=!chown app-user:www-data /run/app-user/app.sock
ExecStartPost=!chmod 770 /run/app-user/app.sock