Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Question: Ideal Secure Folder structure for Django on RedHat or Fedora #849

Open
SomeAB opened this issue Jul 23, 2023 · 17 comments
Open

Question: Ideal Secure Folder structure for Django on RedHat or Fedora #849

SomeAB opened this issue Jul 23, 2023 · 17 comments

Comments

@SomeAB
Copy link

SomeAB commented Jul 23, 2023

Hi, I'm working on Fedora 38, already have apache with mod_wsgi installed (built mod_wsgi from source, instead of pip). MariaDB for DB.

Now my question is this that what is the ideal folder structure for securely running Django with virtual environment

Currently:
/var/www is owned by root:root
/var/www/mypy/pyvenv_01 is the python virtual environment, where /var/www/mypy is owned by user:user
/var/www/djangosite/project1 is the first python project created, where /var/www/djangosite is owned by user:user

I was looking at your answer here, from 10 years ago.. but not sure if its still completely correct
https://stackoverflow.com/questions/16408079/secure-django-file-permissions

I was additionally looking at Thomas Ward's reasoning about file permissions here
https://askubuntu.com/questions/767504/permissions-problems-with-var-www-html-and-my-own-home-directory-for-a-website/767534#767534

mod_wsgi documentation briefly mentions placing the venv in /usr/local/venvs/example
https://modwsgi.readthedocs.io/en/develop/user-guides/virtual-environments.html

Django documentation, just says put it in /home/mycode .. HOWEVER I read somewhere, that this is not good.
https://docs.djangoproject.com/en/4.2/intro/tutorial01/

For now I'm planning to run only Django based website/app .. but I need room to be able to add other stuff as well (nothing particular in mind yet)

It would be really helpful, if you can shed some light on this Mr. Graham.

@GrahamDumpleton
Copy link
Owner

The recommendations in the first StackOverflow post are still valid.

As to Thomas Ward's post, do keep in mind that that post relates to static HTML or maybe PHP code. You should never put Python project code of your application under the Apache DocumentRoot directory (eg., /var/www/html, but can differ based on Linux/Apache distribution). The only exception to this might be the single WSGI script file which loads your application. The code in that should be kept to an absolute minimum and should only import the application object from the real WSGI application entry point for your Python web application, kept outside of the Apache document root.

The location of the virtual environments in the third post are an example only and not a recommendation. You can keep it collocated with your project code if you want.

Important thing is not to rely on anything under your personal home directory. Do not open up permissions on your home directory or use groups to provide access to it. So do not put a distinct virtual environment under your home directory and do not use per user Python package installs.

@SomeAB
Copy link
Author

SomeAB commented Aug 27, 2023

@GrahamDumpleton

So following on your advice, I plan to do the following:

  1. Create a dedicated user (non-admin, normal user) -- Question.. is that alright ? Being non-admin?
  2. use the 'user=name' parameter in Apache config file to define what user the daemon process runs as.
  3. Create the Django project & Python Virtual Environment in /home/NewUser
  4. Minimal .wsgi that loads the application in document root of Apache.
  5. Static/Public files like Favicon, Frontpage html elements to be in document root of apache, and just pointing to it inside Django settings.

Am I missing anything?

@GrahamDumpleton
Copy link
Owner

If when you say "non-admin" you mean a user that does not have "sudo" access, then yes. It should not be a user that has any extra privileges.

As to a WSGI script file, it doesn't need to be, and shouldn't be under the Apache document root (/var/www/html) if possible as then it could be viewed as a static raw file.

Create a separate directory (eg., /var/www/wsgi-scripts) under the Apache root and the WSGIScriptAlias can refer to it from that separate directory.

The only time would put the WSGI script file under the Apache document root is if you had no choice and had to use AddHandler directive for some reason, which wouldn't happen in your case.

@SomeAB
Copy link
Author

SomeAB commented Aug 28, 2023

@GrahamDumpleton yeah, thats very helpful & clear.
But I ran into an interesting problem/situation as a result:

  1. I created a new user called djangouser

  2. Logged in as djangouser and created the 2 folders:
    /home/djangouser/mypyenv -- to store python environments in one place
    /home/djangouser/djangoprojects -- to store Django projects
    where
    /home/djangouser/djangoprojects/project_01/project_01/wsgi.py is the location of the wsgi file of django sample project

  3. Now I'm guessing the since I'm going to run Django as this non-privileged user, now I need to install HTTPD/Apache for this user, and mod_wsgi as well? or maybe not? I already installed it in the main administrator account ..

I'm hoping to not to have to start all over again, since I already have httpd+mod_wsgi installed as the admin user called someab in my case.

  1. However the more important QUESTION that arises is that the directive "user=name" according to mod_wsgi documentation requires that Apache is started as a root user. I guessing that means & merely requires that I do

sudo systemctl start httpd .. and nothing is required. Thats basically the command to start a service on Fedora/Redhat Linux.

PS: Please rest assured, I am compiling all the knowledge you are providing into a video tutorial/walkthrough .. to help those who may want to install the whole stack or just a part of it. I am hoping it would be helpful to the community at large. Thank you so far!

@GrahamDumpleton
Copy link
Owner

You don't need a separate Apache/httpd installation or instance.

When systemd starts Apache/httpd, it starts it as root, but after initial configuration and forking of child processes it drops privileges and the Apache worker processes run as a separate Apache user.

When you specify user=name for mod_wsgi daemon processes, those separate mod_wsgi processes (which are also created and managed by Apache httpd parent process), instead of being run as the Apache user will run as the user you specify instead.

It is normal that Apache runs as root initially as that is the only way it can acquire use of system ports such as 80/443, open log files, and run child processes as a different named user etc. So you don't need to be concerned about that and it is secure.

@SomeAB
Copy link
Author

SomeAB commented Aug 28, 2023

@GrahamDumpleton

Cool. A small question that arises at the end of that is ... what is the standard or ideal way of checking that mod_wsgi is actually running as the user we defined i.e., the directive is being 'ignored' or 'implemented'.

@GrahamDumpleton
Copy link
Owner

See https://www.geeksforgeeks.org/python-os-getuid-and-os-setuid-method/

@SomeAB
Copy link
Author

SomeAB commented Sep 4, 2023

@GrahamDumpleton
So I am in last stage of Deployment, and I ran into another file permissions scenario or two.

First the CONTEXT:
Since django is being served from filesystem of second non-priveledged user called djangouser
the filesystem is like this: /home/djangouser/djangoprojects/pyproject_01

But I'm getting this error when I try to access my 127.0.0.1

[core:error] [pid 7092:tid 7264] (13)Permission denied: [client 127.0.0.1:57598] AH00035: 
access to / denied (filesystem path '/home/djangouser/djangoprojects') 
because search permissions are missing on a component of the path 

this is my custom additional httpd config file:

#Custom Httpd Config File by SomeAB for Django  

#Since WSGI Daemon Process directive is outside of VirtualHost container below 
#Any WSGI app can run under processes created by it, & vice-versa
#And any errors get logged in apache main errorlog, & vice-versa
WSGIDaemonProcess djangodaemon_p user=djangouser processes=4 threads=4 maximum-requests=10000 request-timeout=180 lang='en_US.UTF-8' locale='en_US.UTF-8' python-home=/home/djangouser/mypyenv/pyenv_01 python-path=/home/djangouser/djangoprojects/pyproject_01

#Use mod_wsgi in daemon mode, with given process name
WSGIProcessGroup djangodaemon_p

#Set Application Group to empty string, so all apps use same Python Interpretor
WSGIApplicationGroup %{GLOBAL}

#Linking folders from where to serve public static & media files
Alias /static/ /home/djangouser/djangoprojects/pyproject_01/static/
Alias /media/ /home/djangouser/djangoprojects/pyproject_01/media/

#Linking URLs to Django WSGI File 
WSGIScriptAlias / /home/djangouser/djangoprojects/pyproject_01/pyproject_01/wsgi.py

#Limit request size to server to 100MB, written in bytes here. Optional Safety feature. If exceeded Error 413 is thrown.
LimitRequestBody 1048576

<VirtualHost *:80>
    ServerName sampleapp.com
    ServerAlias www.sampleapp.com
    ServerAdmin admin@sampleapp.com

    #Identify files with .wsgi extention as WSGI Scripts
    AddHandler wsgi-script .wsgi    

    <Directory /home/djangouser/djangoprojects/pyproject_01/pyproject_01>  
        #Strict no override in this directory
        AllowOverride None
        Options ExecCGI MultiViews Indexes
        MultiViewsMatch Handlers
        Require all granted
        
        #Only open up access to wsgi.py
        #<Files wsgi.py>

        #</Files>
    </Directory>

    #Opening up access on the public static files folder
    <Directory /home/djangouser/djangoprojects/pyproject_01/static>
        Require all granted
        #Options Indexes FollowSymLinks
    </Directory>
    
    #Opening up access on the public media files folder
    <Directory /home/djangouser/djangoprojects/pyproject_01/media>
        Require all granted
        #Options Indexes FollowSymLinks
    </Directory>
</VirtualHost>

QUESTION 1: I'm not 100% sure (but pretty sure), that it seems like Apache is not able to access the .wsgi file? I already tried setting permissions of 755 to the folders & wsgi.py for testing purpose.
There are 3 solutions I heard about for this situation:
A. Someone mentioned that Apache should be made 'GROUP' owner of the djangoprojects folder.
B. Someone mentioned that Apache should be added to the personal 'GROUP' of djangouser
C. I myself am considering CREATING a custom GROUP and ADDING BOTH Apache & djangouser to that GROUP, and SETTING that as the owner GROUP. Such that .. this group would only have access in the limited areas.. instead of everywhere Apache or djangouser has access to.

Am I missing something here?

QUESTION 2: Interesting according to Django documentation for SCRIPTALIAS we use wsgi.py .. which is a python script and not a wsgi script. Is this serving as an entry point that you talked about earlier? or is it still treated as a wsgi script..

So I am guessing in this case I don't need 'Add Handler wsgi-script .wsgi ' directive at all? Or is this still expected by mod_wsgi somewhere?

QUESTION 3: As you may notice in my httpd config, to further close down permissions, I was planning on using Inside the expected directory/location to only open up permissions there. Will mod_wsgi have issues with this?

My main concern is that the project's settings.py is also in that folder, and exposing that doesn't seem like a good idea. That is why I prefer to use to isolate permissions.

@GrahamDumpleton
Copy link
Owner

You definitely do not need Add Handler wsgi-script .wsgi.

What do you get when you run:

ls -alsd /home/djangouser

The safest way is NOT to have WSGIScriptAlias directly map to wsgi.py in your project code area.

As I said before, have a /var/www/wsgi-scripts which contains project_01.wsgi and then use:

WSGIScriptAlias / /var/www/wsgi-scripts/project_01.wsgi

<Directory /var/www/wsgi-scripts>  
        AllowOverride None
        Require all granted
</Directory>

You don't need the Directory block for /home/djangouser/djangoprojects/pyproject_01/pyproject_01 then and not opening up any access to where your code is.

The project_01.wsgi file would include only:

# Import wsgi.py file from Django project.
from project_01.wsgi import application

and if necessary this could configure host specific environment variable configs etc.

@SomeAB
Copy link
Author

SomeAB commented Sep 4, 2023

@GrahamDumpleton
When I do the ls command (while logged in as djangouser) .. I get

0 drwx------. 1 djangouser djangouser 554 Sep  4 11:08 /home/djangouser

Q1:Does this mean I have to open up the home directory even at top level?

I'm doing it the way you suggested.. since I want to have the safest route.

Q2: Now since project.wsgi is supposed to have .wsgi extension.. so we need the Add Handler? If not, how will it recognize wsgi scripts? Is there something else that is already doing it?

Q3: I created project_01.wsgi under /var/www/wsgi-scripts
both the folder and the file have 755 permissions

About what is inside it (project_01.wsgi), I think you meant

import os

from wsgi.py import application

and NOT just

from project_01.wsgi import application (because that would be referencing the file from within itself?)

Also, I made the changes to the django_httpd.conf (as you suggested)
https://paste.pythondiscord.com/ZMRA

@GrahamDumpleton
Copy link
Owner

When the home directory is not readable to others like that, the Apache user cannot serve static files out of there when using Alias directive. The WSGIScriptAlias would also likely fail. If you want the home directory to be locked down, you would need to have the static and media directories outside of the user home directory and accessible to the Apache user somehow.

You do not need AddHandler even if the file uses a .wsgi extension when used with WSGIScriptAlias. You could also the extension .python-script if you want with WSGIScriptAlias and it would still work without needing to set AddHandler for .python-script. The AddHandler is only required when you want .wsgi scripts in a directory referenced using Alias directive to be handled in a special way.

No I did mean:

from project_01.wsgi import application

That will look for the Python package with directory name project_01, containing the file wsgi.py and import the function called application within it.

It would work because you had:

python-path=/home/djangouser/djangoprojects/pyproject_01

thus it will find /home/djangouser/djangoprojects/pyproject_01/project_01./wsgi.py.

If you used:

from wsgi.py import application

It would look for a Python package with directory name wsgi, with a file called py.py inside of it.

So Python package/module imports are different and wsgi.py in that context does not refer to a file called wsgi.py.

@GrahamDumpleton
Copy link
Owner

You also should not need:

Options ExecCGI MultiViews Indexes
MultiViewsMatch Handlers

in the Directory block when using WSGIScriptAlias.

@GrahamDumpleton
Copy link
Owner

Also a bad idea to use maximum-requests=10000.

If you site experiences very high throughput then this only serves to cause the processes to restart more often, which is bad when you are under load.

If you want to restart periodically, you are better off looking at restart-interval. Look at using graceful-timeout in conjunction with this to avoid breaking running requests.

@SomeAB
Copy link
Author

SomeAB commented Sep 4, 2023

@GrahamDumpleton Most interesting indeed!!

I get the rest of it, but the question about where to serve the static and media files from remains. This 'djangouser' does not have any sensitive files (since it was created for the sole purpose of running mod_wsgi+Django as ) except the python virtual env and Django files themselves

I am assuming that the static/media folder is being accessed as 'Apache' because we are using Alias ? So Apache is serving them directly?

One possible solution that comes to mind is to make a static/media folder under /var/www (chown djangouser? 755 for perms?) to do Django's 'collectstatic' command on it

Or opening up home folder of djangouser is fine?

@SomeAB
Copy link
Author

SomeAB commented Sep 4, 2023

This is my current django_httpd.conf
https://paste.pythondiscord.com/OW4Q

Edit: For testing only (until your answer) I did go ahead and chmod /home/djangouser to 755, and fixed a typo that was causing me issue.. and now django starter page is perfectly fine.. on 127.0.0.1

@SomeAB
Copy link
Author

SomeAB commented Sep 8, 2023

@GrahamDumpleton I am hoping to hear from you regarding this part. I am not sure if opening up perms is the right way.

@GrahamDumpleton
Copy link
Owner

Not much else I can tell you. There is no ideal folder structure as each has it's own pros and cons.

Opening up permissions for the home directory of the user reduces security a little, but might be more convenient than having to generate static files to a directory under /var/www from your Django project.

The safest scenario is where the Apache user would never be allowed to serve up or even read directories containing your Python source code. Also that whatever user runs your code under Apache cannot write to directories holding the source code. The only thing the user your code runs as might need write access to is media upload directories or a database directory if using sqlite.

The thing is though, is that Apache itself is unlikely to be your weakest point. What you have to worry about more is that the Python code of your web application is written correctly and doesn't have vulnerabilities, especially if it interfaces with a database. You seemed to be spending a lot of time worrying about Apache when you should be worrying about your Python web application code more.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants