nginx / unit

NGINX Unit - universal web app server - a lightweight and versatile open source server that simplifies the application stack by natively executing application code across eight different programming language runtimes.
https://unit.nginx.org
Apache License 2.0
5.37k stars 323 forks source link

Ability to specify user/group for routes #505

Closed smtalk closed 1 year ago

smtalk commented 3 years ago

Yes, I know, it sounds like a weird request. However, we'd like to be able to specify user/group for routes for security purposes.

Let's say we have a route:

        "aaba.com/test": [
            {
                "action": {
                    "share": "/home/admin/domains/aaba.com/static/"
                }
            }
        ],

If I have user-owned symlink: lrwxrwxrwx 1 admin admin 11 Nov 26 21:14 index.html -> /etc/passwd

Or: lrwxrwxrwx 1 admin admin 11 Nov 26 21:14 index.html -> /home/anoteruser/public_html/wp-config.php

It just returns me the content of the file in aaba.com/test. There is no such thing as "SymlinkIfOwnerMatch" (Apache thing) in unit at this time.

And it's only one of the problems. Another case can be: "action": { "proxy": "http://unix://home/admin/nodejs.sock" }

It'd simply fail with "Permission denied", unless I make the socket readable by user "nobody". What if I'm user admin, and I don't want any other users to read my socket? (or at least I don't want "nobody" to have ability to read it)

Alternatively - let "static" content or proxy be an "app" (and route would just pass to it), that way it'd run under user/group + even be in chrooted namespace if configured.

I'll be waiting for your input. Thank you!

VBart commented 3 years ago

You can do that already.

The default user and group are specified at compile time via --user= and --group= options of ./configure script (see here for details), or they can be overridden at runtime using --user and --group arguments to unitd (see here for details).

smtalk commented 3 years ago

Yes, I know, but I cannot make "admin" user serve static files from /home/admin/public_html and user "vbart" serve files from /home/vbart/public_html, can I? Without ability to do this, both security issues are still there on shared environments.

VBart commented 3 years ago

@smtalk You just need to add user of Unit daemon to primary groups of both vbart and admin.

For example, let's vbart have vbart group, admin have admin group, and Unit daemon are run as unit. Then you have to add unit user to vbart and admin groups. If group permissions are properly set to those directories, then unit will have access to both directories, while users will have access only to their own.

smtalk commented 3 years ago

@VBart this does not solve the problem :) For example, I have unit added to vbart and admin groups, and it's still able to read root owned /etc/passwd or any other world-readable file on the system that file privileges allow to read. What's the point of full app isolation if simple symlink can read files from other users (if permissions allow do do it?).

I think full isolation of routes would require major recoding of that part. So, maybe at least a protection could be added to the next release not to follow the symlinks if the owner differs? If symlink is owned by user:user - don't follow the symlink to a file owned by anotheruser:anotheruser. This simple check would be enough to cover the basic security needs.

Thank you!

VBart commented 3 years ago

@smtalk There's no way to change permissions per request without significant performance degradation, that's why it doesn't make much sense to implement.

Note, that Apache option SymlinkIfOwnerMatch won't help you too in this case. I'll quote the Apache documentation here:

This option should not be considered a security restriction, since symlink testing is subject to race conditions that make it circumventable.

It has serious race condition in its implementation, that effectively allows attacker to ignore any restrictions.

Currently, the best you can do with Unit is either putting it inside a container/chrooted environment or disable symlinks via mount options for users directories.

We can implement something similar to nginx directive disable_symlinks with if_not_owner option, but I should warn you that enabling it also leads to performance degradation.

smtalk commented 3 years ago

@VBart yes, you've got the point, I had something like the following in my mind in latest posts: disable_symlinks if_not_owner from=$document_root;

Providing a way to define static content serving rules directly in the app, even if it's way slower would be the most awesome though, that way @i4ki 's linux-namespace implementation would be in effect, including chroot. App is always running anyway, so, no overhead of the namespace creation. The ones who prefer safety vs. speed would have an alternative, in case they have an app which is 'associated' with the static files. That way whole linux namespace feature (with chrooting) could be used to serve the static content. Unit would be able to read files that are user-owned, and they wouldn't need to be readable by nobody user anymore (a bonus?). And everyone who wants just to serve static content without any app could use the current method.

Anyway. nginx-like disable_symlinks is way better than nothing on shared hosting envs if there is no time for any other solutions. And it's pretty obvious that checking file type, ownership (and ownership of the target file) with every request slows the things down, but it's really acceptable on shared envs.

Thank you, I'm still surprised how you find time for so detailed and fast responses 👍

VBart commented 3 years ago

@smtalk That's not about checking only files ownership and type. This way Apache works and this way it has race condition. For proper check you have to follow the whole path to the file, opening and checking each directory.

Currently, you can serve static files from the app as well. In order to do that don't need anything specific from the Unit side. You can write an app that will work only as a static file server in any language supported by Unit.

smtalk commented 3 years ago

@VBart I get it. The thing is we adapt Unit for masses in a web hosting control panel, it's not to have it for a specific app or own server. If it was for our own needs - the request wouldn't be here. We simply cannot ask end-customers to add a static file server to their wordpress installations and so on. We just want a feature to safely serve static content for them from unit, without offloading static content to Nginx (or Apache). Without this feature the only real solution would be to serve static content on another webserver, but this doesn't fit well with standalone-Unit case.

If you see no point to have an option to serve static content safely for other users of Unit, and if you don't see linux namespaces (+chrooting) codebase in Unit underutilized (because it might fit static content case perfectly, and be a little unique, because I know no other webserver doing this?) - we'll just try 'workarounding' this by not letting customers run Unit as a standalone webserver and serving static content directly in another webserver. I just wanted to know your opinion and plan the final things of nginx unit implementation in control panel.

VBart commented 3 years ago

@smtalk The problem here and I guess that also explains why you don't see similar features in other servers as well: loosing popularity of shared hosting and strong trend on clouds. That's what we see for the last few years. The disable_symlinks directive in nginx was introduced in 2011 and now I can't remember when I see it used by someone last time. Nowadays it feels like forgotten and rarely used directive. Almost everybody just use cloud-hosting, or rent a cheap VM, and never having any shared-hosting related problems.

VBart commented 3 years ago

We simply cannot ask end-customers to add a static file server to their wordpress installations and so on. We just want a feature to safely serve static content for them from unit, without offloading static content to Nginx (or Apache).

But if you don't let them having their own Unit instance that they can fully control and that you can isolate, then you don't need to ask to add anything to their WordPress installation. You can automatically run such static server app with the same permissions as their WP instance.

Basically, if we would implement something like that, then the implementation will be similar: we will have to write an app that acts like a static server. Of course, it probably will be embed in any app module, but it just an implementation detail. Separate app here won't be much worse.

smtalk commented 3 years ago

@VBart I won't argue on this, even if our product is growing a lot (past 17 years), it wouldn't mean your statements aren't true.

I did not mention solutions like mod_ruid2 or MPM-ITK, because they aren't so nice and pose other security risks. I specifically had linux namespaces in my mind. Apache/Nginx have no native support for linux namespaces/sandboxing.

Forget the hosting and my other statements. What I want to say is that it sounds really weird to me that I can run a PHP app in a very secure environment, seeing nothing else, just the app and it's files, then I just execute:

<?php
symlink('/etc/passwd','read.txt');
?>

Access read.txt over static file router and that namespace/chrooting loses its potential, as I see something app couldn't see natively.

"app like a static server" sounds great, but.. it's a 2nd namespace and 2nd process, a bit of waste of resources I think, but.. way better than nothing.

smtalk commented 3 years ago

@VBart I also forgot to add something to the discussion of the "uniquenesses". Even application server Phusion Passenger has no support of chroot or linux namespaces, see their discussion to someone here: https://github.com/phusion/passenger/issues/307.

Quote: It made some sense in the past, but now that we have containerization technologies such as LXC and Docker, chroot is effectively obsolete.

However, I'm very glad Nginx Unit did not take that approach, and this uniqueness is pretty beneficial in many other environments, not just hosting.

Another request like https://github.com/nginx/unit/issues/494 would add another uniqueness there, and it would attract even more end-customers with different use-cases.

See https://twitter.com/solomonstre/status/1111004913222324225, in short (yes, maybe a bit overhyped): If WASM+WASI existed in 2008, we wouldn't have needed to created Docker.

Just wanted to say that "almost everybody just use cloud-hosting with containerized apps in docker" isn't the answer to everything, and I'm glad to see nginx-unit current feature-set not following the same route as passenger, as this differentiates the products and strengthens Unit. In another git issue you've told me that you don't see many Go/NodeJS users in Unit user-base (and like "chicken-or-the-egg" problem), it doesn't mean Go/NodeJS aren't popular or that their days are over :) And I'm pretty sure that whenever we finish stable/final integration with Unit, you'll see a spike in number of "hosting environments". The day Go/NodeJS issue with update-dependencies get solved - you'll see more "Go/NodeJS" environements. And whenever you have X-Forwarded-For implemented - you'll see more usage in the mentioned clouds and proxied environments. It's all about the possibilities (and yes, user-base that could use them).

Current Unit strengths to me are the uniquenesses it has, and that they were the things that attracted me the most! So, just a kind "thank you" for getting them here :) And I'm really not pushing you on anything, as anyone can be wrong with their predictions, that's why the discussion is here, which I really appreciate!

VBart commented 3 years ago

@smtalk

What I want to say is that it sounds really weird to me that I can run a PHP app in a very secure environment, seeing nothing else, just the app and it's files, then I just execute

It's easily fixed by not putting your static files to the application working directory.

There's no reason to mess app code with static media. Actually most web frameworks and apps don't have that mess and use a separate directory for public. However, there're bunch of apps and frameworks (mostly PHP) that mix .php with other files under the same directory, which is a very bad practice actually.

This bad practice leads to many security flaws, not only that one you mentioned. For example, you can accidentally serve the source code of your app as a static file. Even if you configured your server to avoid serving .php, but you're using editor that leaves some tmp/backup files like .php~ and these files can be served as static, exposing your code or configuration, or any other sensitive data. There a lot of ways to fail if you put your code in public document root directory for static serving.

Moreover, such mess complicates configuration a lot.

Even if the app is so bad written and mix static files with the code files, there're ways how to fix that:

  1. You can extract those files from the app.
  2. Put the whole app with the Unit inside a container.

Having static media separated also allows you to serve it from different machine or easily offload to CDN, which nowadays pretty common.

smtalk commented 3 years ago

@VBart I fully agree with you and your statements, the problem - the frameworks/CMS you've mentioned.

Even some of the ones having static files served in another directory - I haven't checked every app mentioned on https://unit.nginx.org/howto/#applications, is there any app which couldn't reach its own static files dir? If it could, that dir would be owned by the user, and it'd still suffer from the same issue. I've opened 3-4 of them to check, but.. all had static content "mixed". In most cases the app would be able to write to the static content dir I think, because if the app allows uploads locally (not talking about the cases where it uploads to external file server/storage) - write privilege is there, and any code injection would let create the symlink and access it over static content router.

Anyway, we're discussing good/bad practices here. I wish everyone used only good practises and isolate everything as much as they can on the app level, however, the reality is different, that's why things like linux namespaces, ACLs and other things are used. Maybe even 2FA authentication or spam filters wouldn't be needed nowadays if everyone would follow good practices and do the things right :)

VBart commented 3 years ago

@smtalk I agree with you and yes, we'd probably add similar to disable_symlinks directive.

But serving static from the app processes is a nightmare from implementation point of view. In order to support Go/Node.js (and other possible stand-alone apps), we had to write a separate lightweight library (libunit), that is loaded into apps and implements all the IPC protocol for passing requests and web-sockets. Thus, we can't use any arbitrary functions from Unit in runtime, especially after execv() call: we don't have access to its config (to MIME types, for example) and very limited in what we can do. So it literally means writing and supporting another separate static file server almost from scratch.

That's why I suggest you just to do the same for your needs. Or just wait for disable_symlinks.

smtalk commented 3 years ago

@VBart disable_symlinks is enough for the basic (and our) needs, if this can be added into the next releases - this would be awesome!

Static file-server as an app sounds great for additional protection (static file serving case), and for cases where customers might want user-owned process to proxy to some secure UDS, as currently the socket needs to be readable by the user unit is running as. Use case: I want my NodeJS app to listen on a UDS, and I just want unit to proxy to it. I don't want that socket to be readable by others. Simple user ownership and 600 chmod would be sufficient to serve it over unit and control in Unit who and how can access it over http/https. So, adding it to some distant-future plans would be great, unless you see no point for it.

Thanks again.

VBart commented 3 years ago

I want my NodeJS app to listen on a UDS, and I just want unit to proxy to it. I don't want that socket to be readable by others

This is solvable by the permissions scheme I mentioned in the comment https://github.com/nginx/unit/issues/505#issuecomment-734505896 above.

Your NodeJS will have its own personal group and user, then you can give access to the group without letting others to have access to it. Only Unit user will be in that group.

smtalk commented 3 years ago

I want my NodeJS app to listen on a UDS, and I just want unit to proxy to it. I don't want that socket to be readable by others

This is solvable by the permissions scheme I mentioned in the comment #505 (comment) above.

Your NodeJS will have its own personal group and user, then you can give access to the group without letting others to have access to it. Only Unit user will be in that group.

Yes, but it's similar to solving "static content load" problem by using containers. I mean it's one of 'you can make things work another way' things.

  1. Group permissions would need to be set for the socket (even unit control sock has no group permissions, and I principle of least privilege was there), so, chmod 600 would not be enough, group would need to be allowed to have privileges (and, of course, privileges to enter the directory).
  2. Unit user would be able to read any other sockets, backend of an interface controlling "unit" (whatever hosting, cloud or providers use) would need to check the permissions/location of the socket and forbid anyone else proxying to them, it's nowhere as safe as inability to use a socket file owned by someone else.
  3. Every user group needs to be modified and have unit in for this 'functionality' to be available for everyone..
  4. If unit supported serve of the static files as user, they wouldn't even need to be readable by 'others' to be served. If I had wordpress, I could even have all the files chmod 600, and they'd be served without the problems. If I add "unit" to every group - unit would be able to read each other user files, and it's sill some backend controlling what can be written as the path of "static:" configuration directive and what not.

Anyway, as I said - if you see no point there, just skip it :) It'd just open more possibilities for us and others, but workarounds are always possible.

Thanks, I guess case can be closed.

VBart commented 3 years ago

@smtalk chroot, follow_symlinks, and traverse_mounts options were added in https://github.com/nginx/unit/commit/53279af5d44dce2b679399d6a36eb46292928175 (will be released as 1.24.0 by the end of the month). I assume that's one of the solutions you asked for.

VBart commented 3 years ago

@smtalk chroot, follow_symlinks, and traverse_mounts options were added in 53279af (will be released as 1.24.0 by the end of the month). I assume that's one of the solutions you asked for.

Released with Unit 1.24.0. See the docs: https://unit.nginx.org/configuration/#path-restrictions

tippexs commented 1 year ago

Closed because solved with a release.