aiohttp icon indicating copy to clipboard operation
aiohttp copied to clipboard

UNIX socket permission bits

Open andreymal opened this issue 6 years ago • 13 comments

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? :)

andreymal avatar Oct 07 '19 15:10 andreymal

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(...)

asvetlov avatar Oct 08 '19 12:10 asvetlov

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 avatar Oct 08 '19 17:10 webknjaz

@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.

andreymal avatar Oct 08 '19 18:10 andreymal

@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 avatar Oct 08 '19 18:10 andreymal

@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?

asvetlov avatar Oct 08 '19 18:10 asvetlov

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 avatar Apr 16 '20 17:04 vitto32

@vitto32 that should work, I guess. But the privileges seem too broad. Though it's up to you and depends on your needs.

webknjaz avatar Apr 20 '20 15:04 webknjaz

@webknjaz yep that works. I know the privileges are broad, but nginx and vhost users have different groups (in plesk).

vitto32 avatar Apr 20 '20 15:04 vitto32

666 works on my machine with nginx! I think it would be great if run_app did this automatically.

louisabraham avatar May 17 '20 02:05 louisabraham

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?

guusbertens avatar Jun 11 '20 23:06 guusbertens

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.

guusbertens avatar Jun 12 '20 22:06 guusbertens

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.

samthesuperhero avatar Sep 17 '20 06:09 samthesuperhero

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

Dreamsorcerer avatar Aug 16 '24 23:08 Dreamsorcerer