httpd-wiki-changes mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Apache Wiki <wikidi...@apache.org>
Subject [Httpd Wiki] Update of "ExtendingPrivilegeSeparation" by jmcg
Date Sun, 12 Jul 2009 15:08:00 GMT
Dear Wiki user,

You have subscribed to a wiki page or wiki category on "Httpd Wiki" for change notification.

The following page has been changed by jmcg:
http://wiki.apache.org/httpd/ExtendingPrivilegeSeparation

New page:
## Please edit system and help pages ONLY in the moinmaster wiki! For more
## information, please see MoinMaster:MoinPagesEditorGroup.
##master-page:Unknown-Page
##master-date:Unknown-Date
##acl MoinPagesEditorGroup:read,write,delete,revert All:read
#format wiki
#language en
== Introduction ==
This '''DRAFT''' httpd setup is inspired by [http://wiki.apache.org/httpd/DifferentUserIDsUsingReverseProxy
DifferentUserIDsUsingReverseProxy]. However it takes the idea a couple of steps further, and
describes other, everyday aspects one might stumble upon. It's been tested for more than a
year now, is very stable, scalable and most imporantly: secure.

The first deviation from [http://wiki.apache.org/httpd/DifferentUserIDsUsingReverseProxy DifferentUserIDsUsingReverseProxy],
is to  run all backends on high-ports (as it has only later been added). This has two consequences:
 1. All backends can be started and run as unprivileged users, no privilege escalation can
happen from malicious scripts executed by httpd.
 2. The frontend never executes third party code, it's security concerns are confined within
the limits of the Apache HTTPd.
Furthermore we shall demonstrate how to secure PHP applications without [http://ilia.ws/archives/18_PHPs_safe_mode_or_how_not_to_implement_security.html
safe-mode].

== Bare minimum ==
A great deal of the configurations is shared accross all the instances of httpds, so we'll
show it here:
{{{
ServerRoot "/opt/es"
ServerAdmin webmaster@esotericsystems.at
LoadModule authz_host_module libexec/apache/mod_authz_host.so
LoadModule include_module libexec/apache/mod_include.so
LoadModule charset_lite_module libexec/apache/mod_charset_lite.so
LoadModule env_module libexec/apache/mod_env.so
LoadModule mime_magic_module libexec/apache/mod_mime_magic.so
LoadModule expires_module libexec/apache/mod_expires.so
LoadModule headers_module libexec/apache/mod_headers.so
LoadModule setenvif_module libexec/apache/mod_setenvif.so
LoadModule mime_module libexec/apache/mod_mime.so
LoadModule negotiation_module libexec/apache/mod_negotiation.so
LoadModule dir_module libexec/apache/mod_dir.so
LoadModule alias_module libexec/apache/mod_alias.so

<Directory />
    Options FollowSymLinks
    AllowOverride None
    Order deny,allow
    Deny from all
</Directory>

DirectoryIndex index.html

<FilesMatch "^\.ht">
    Order allow,deny
    Deny from all
    Satisfy All
</FilesMatch>

LogLevel warn

DefaultType text/plain
ExpiresActive on
TypesConfig etc/apache/mime.types
MIMEMagicFile etc/apache/magic

Header append Vary User-Agent env=!dont-vary

Include etc/apache/extra/httpd-mpm.conf
Include etc/apache/extra/httpd-languages.conf
Include etc/apache/extra/httpd-default.conf
}}}
With this simple config any host can serve static content at the very least. If it's not supposed
to do any more than that, it will do it without any kludge. This is all it needs to perform
the task.

== Frontend ==
{{{
# include base config
Include /opt/esetc/apache/httpd.conf
Include /opt/es/etc/apache/extra/httpd-proxy.conf
Include /opt/es/etc/apache/extra/httpd-deflate.conf
#Include /opt/es/etc/apache/extra/httpd-cache.conf
# only the proxy does CustomLogging!
Include /opt/es/etc/apache/extra/httpd-log.conf
# listen to UID
Listen 80
Listen 443
User www
Group www

# supply PID and lock file
PidFile "/var/opt/es/apache/proxy/proxy.pid"
LockFile "/var/opt/es/apache/proxy/proxy.lock"

ErrorLog "|/opt/es/bin/rotatelogs -l /var/opt/es/apache/proxy/error_log.%Y%m%d 86400"
CustomLog "|/opt/es/bin/rotatelogs -l /var/opt/es/apache/proxy/access_log.%Y%m%d 86400" vhostcombined
env=!dontlog

ServerName borscht

NameVirtualHost *:80

<VirtualHost *:80>
        ServerName esotericsystems.at:80
        ProxyPass / http://127.0.0.1:8001/ disablereuse=on
        ProxyPassReverse / http://127.0.0.1:8001/
</VirtualHost>
<VirtualHost *:80>
        ServerName omfzd.tld:80
        ProxyPass / http://127.0.0.1:8002/ disablereuse=on
        ProxyPassReverse / http://127.0.0.1:8002/
</VirtualHost>
# etc...
# SSL VHosts:
Include /opt/es/etc/apache/extra/httpd-ssl.conf
<VirtualHost 1.2.3.4:443>
        ServerName insecure.org:443
        RequestHeader set X_FORWARDED_PROTO 'https'
        SSLEngine On
        SSLCertificateFile "/opt/es/etc/certs/server.insecure.org.cert"
        SSLCertificateKeyFile "/opt/es/etc/certs/private.insecure.org.key"

        ProxyPass / http://127.0.0.1:8003/ disablereuse=on
        ProxyPassReverse / http://127.0.0.1:8003/
</VirtualHost>
# etc..
}}}
We'll skip the explanation of the obvious, and come straight to the {{{CustomLog}}}. We only
log in the front-end. And even here, we only have '''one''' {{{CustomLog}}}, effectively reducing
the number of open handles.

You might notice the use {{{:80}}} in the {{{ServerName}}}, this has proved to be a workaround
for some applications, wel'll see more of this in the backends.

{{{disableruse=on}}} is a workaround for [https://issues.apache.org/bugzilla/show_bug.cgi?id=45362
PR#45362]

We can also use the frontend as SSL Terminator, leaving the backend to concentrate on it's
real business, not on encryption.

== Backends ==
Everything can be a backend. Even though in the above example I've only shown {{{http://}}}
for {{{ProxyPass}}}, this doesn't keep you from running mongrels, or tomcats (and thus to
use {{{ajp://}}}) in the backend.

As we're concentrating on Apache HTTPd, we'll show some examples with that, as well as it's
peripherals.
=== Base Config ===
All backends have a certain config style in common, and we'll first show that (from a template)
to outline the basic idea:
{{{
# include base config
Include /opt/es/etc/apache/httpd.conf
# listen to UID
Listen 127.0.0.1:UID
User template.tld
Group template.tld
ServerName template.tld

# include other useful stuff:
Include /opt/es/etc/apache/extra/httpd-multilang-errordoc.conf
Include /opt/es/etc/apache/extra/httpd-php.conf

# supply PID and lock file
PidFile "/var/opt/es/apache/template.tld/pid"
LockFile "/var/opt/es/apache/template.tld/lock"
ErrorLog "|/opt/es/bin/rotatelogs /var/opt/es/apache/template.tld/error_log.%Y%m%d 86400"

<Directory /srv/web/template.tld>
        Options +MultiViews
        Allow from All
        AllowOverride None
</Directory>

NameVirtualHost 127.0.0.1:UID

Include /opt/es/etc/apache/vhosts/template.tld/www-httpd.conf
# Maybe Include some more (sub domains...)
}}}
The baseconfig defines a {{{User}}} and a {{{Group}}}, our convention is to name it same as
the {{{ServerName}}}. In the {{{Listen}}} directive we see that this convention is further
translated to listening to this user's UID.

We have one ErrorLog per domain, but if you like to log per vhost, you can of course change
it.

We then define some sane settings for {{{<Directory>}}} where our vhosts will be located,
start off name-based vhosting and start including vhosts.

Before looking into the vhosts, I'd like to dwell on the subject of structuring websites.
We've chosen a rather simple setup:
{{{
/srv/web/omfzd.tld
|-- www
|   |-- htdocs
|   |-- session
|   `-- tmp
`-- intra
    |-- htdocs
    |-- session
    `-- tmp
}}}
First off: Discussing whether or not it's a good idea to have the default vhost be ''www.''
is moot. It's just a convention, you can name it whatever you like.

Putting each domain in one folder, and each of it's subdomains in a sub-folder thereof. This
organization eases the structuring of configurations, the separation of privileges and also
enables you to interface with other daemons such as an ftpd.

We also see here a {{{session}}} and a {{{tmp}}} directory. More on this soon!

=== The VHosts ===
We'll be using the same vhosts as in the front-end example to gradually increase complexity
and show different aspects of the configurations.
==== Simple ====
The most simple of vhosts serves static content and looks like this:
{{{
<VirtualHost 127.0.0.1:8001>
    ServerName http://esotericsystems.at:80
    ServerAlias www.esotericsystems.at
    DocumentRoot "/srv/web/esotericsystems.at/www/htdocs"
</VirtualHost>
}}}
Note that we're using {{{http://esotericsystems.at:80}}} as {{{ServerName}}}, this is very
important for Redirects!

Also some applications take this as a hint where they're really running on, because not many
applications bother to check ''X-Forwarded-For''...
==== PHP ====
PHP is not to be trusted. However running it in safe-mode is just a pain. As we've already
taken care of privilege separation, we'll now go a step further and cut it off from the rest
of the world using {{{open_basedir}}}.

But instead of sharing a common {{{/tmp/}}} for sessions and uploads, we separate those as
well, as already hinted by the folder-structure:
{{{
<VirtualHost 127.0.0.1:8002>
    ServerName http://omfzd.tld:80
    ServerAlias www.omfzd.tld
    DocumentRoot "/srv/web/omfzd.tld/www/htdocs"
    php_admin_value open_basedir /srv/web/omfzd.tld/www/:/opt/es/share/pear/
    php_admin_value session.save_path /srv/web/omfzd.tld/www/session/
    php_admin_value upload_tmp_dir /srv/web/omfzd.tld/www/tmp/
</VirtualHost>
}}}
In {{{open_basedir}}} we have to include all the paths that our PHP application needs access.
If for instance, you're serving a MediaWiki, your {{{open_basedir}}} line would look something
like this:
{{{
php_admin_value open_basedir /srv/web/omfzd.tld/www/:/opt/es/share/pear/:/usr/bin/diff:/usr/bin/convert
}}}
This would allow PHP access to {{{/usr/bin/diff}}}, but also to {{{/usr/bin/diff3}}} and other
variations thereof! Please refer [http://www.php.net/manual/ini.sect.safe-mode.php#ini.open-basedir
open_basedir documentation] for more information, or to the [http://www.php.net/manual/ini.list.php
php.ini documentation] in general.

Another directive we could use here, is [http://www.php.net/manual/configuration.file.php
PHPIniDir]. It would enable us to have an unique per-domain (! Not per-vhost!) {{{php.ini}}}.
==== Complex ====
This example shows our SSL VHosts, it includes a sample for configuring mod_passenger as well
as authentication:
{{{
LoadModule passenger_module /var/lib/gems/1.8/gems/passenger-2.2.2/ext/apache2/mod_passenger.so
PassengerRoot /var/lib/gems/1.8/gems/passenger-2.2.2
# Disable Passenger in Server Context, we'll only enable it where needed
PassengerEnabled off

<VirtualHost 127.0.0.1:8003>
    ServerName http://insecure.org:443
    ServerAlias www.insecure.org
    DocumentRoot "/srv/web/insecure.org/www/htdocs"
    
    RailsEnv production
    RailsBaseURI /projects
    <Location /projects>
           PassengerEnabled on
    </Location>

    <Location />
        Require valid-user
        Authuserfile /srv/web/insecure.org/www/.insec_user
        Authname "Authorized Access Only."
        Authtype basic
    </Location>
</VirtualHost>
}}}
Firs of all, the {{{ServerName}}} in this case is of course {{{https://insecure.org:443}}},
but even that doesn't help much with some applications ([http://www.redmine.org/ Redmine]
in this case).
We thus set: 
{{{
        RequestHeader set X_FORWARDED_PROTO 'https'
}}}
'''in the frontend''' (because I thought it's a more appropriate place) as suggested by their
[http://www.redmine.org/wiki/1/FAQ#Why-does-Redmine-use-http-links-when-I-want-it-to-use-https-links-in-Apache-SSL
FAQ].

As the comments suggest, we disable mod_passenger for the Server Context. We only want it
where we need it, in this case in {{{<Location /projects>}}}.

And finally we can see that authentication requests can be required from the backend. The
frontend will transparently put it through to the clients browsing your website.

On the otherhand, if you have a backend which doesn't know how to deal authentication, but
needs protection, you could do the authentication in the frontend.

== Further Discussion ==
Questions?

I should probably provide the script I use to start the backends. Which is essentially a wrapper
{{{sudo}}} wrapper.

Mime
View raw message