Friday, December 21, 2007

Per User web hosting in Apache and Tomcat

If there are multiples users on a web server, each user may be allowed to host a web site in his home directory. By this way the to access the web site hosted in the home directory of safeer on the server www.myhostingteam.net use the URL: http://www.myhostingteam.net/~safeer. This is most often used in Universities and research labs, where each staff/scholar will have a user account in the organization's web server.

Apache HTTPD

In apache, this is achieved by using the UserDir directive. This feature will be disabled by default. The web contents will be supplied from the directory public_html inside the users home directory. The directive for this is:

UserDir public_html
This directive will be commented out by default.
If the directory path does not start with a leading slash, the path will be considered relative to the user home directory.In this case the user direcory can be anywhere in the file system. Apache will search the user database (/etc/passwd) to find out the home directory of the user and will fetch the web contents from the public_html subdirectory there.

If the path starts with a leading slash, apache will not search the user database, instead the path will be constructed by appending the username to the path given. That is, if
UserDir /home
when you access
http://www.myhostingteam.net/~safeer the contents will be supplied from /home/safeer. This setting has a disadvantage that every world readable file and world executable directory in your home directory will be accessible to the public, if they know that such a file or directory exists. Another disadvantage(?) is that apache will treat every directory under /home as a user. That is, if i create a directory "nouser" in the home directory and the public_html stuff under that, the contents can be accessed as http://www.myhostingteam.net/~nouser.

We can solve the first of these issues by using another format for UserDir:
UserDir /home/*/public_html
The asteric (*) will be replaced by the username. In this and the first approach, only the subdirectory public_html will be accessible by apache, and this is a better choice from a security point of view.
For UserDir to work, the file system permissions should be set properly:
The home directory should be world executable ( chmod 711/a+x),
The files/directories under/including public_html should be
world readable & executable ( chmod 755/a+rx).

We can restrict which user has permission to use this feature by using additional UserDir directives.

As in any access restriction scheme there are two approaches here, mostly open and mostly closed.

Mostly Open:

All users will be granted access, except for somebody,
UserDir disabled safeer nebu
Here, all users except safeer & nebu will be granted access

Mostly Closed:

All users will be granted access, except for somebody,
UserDir disabled
UserDir enabled safeer nebu
Here, all users except safeer & nebu will be denied access


By default the feature is disabled by the single directive UserDir disabled. If a user is listed in both disabled and enabled, he will be denied access.

Tomcat

In tomcat, the user web application configurations(and many other features) are configured using the Listener element which is included in the Host element. A "className" attribute of the Listener element determines what feature is to be implemented. For per user web applications, the className attribute will be "org.apache.catalina.startupe.UserConfig".


Here we have two approaches , in the first user home directory information is collected from the password database (/etc/passwd) and in the second all directories under a particular directory will be considered as user home directories. The later approach is used mainly in windows and other systems that do not have /etc/passwd user database. This approaches are implemented using the userClass attribute of the Listener element. A "directoryName" attribute is used to specify the name of the directory under user home directory where the user web applications will be deployed.

First Scenario:

<Host name=. . . . . . . . . . >
. . . . . . . .
< Listener className="org.apache.catalina.startup.UserConfig"
directoryName="public_html"
userClass="org.apache.catalina.startup.PasswdUserDatabase
" />
. . . . . . . . . . . . < /Host>


Second Scnario:

< Host name=. . . . . . . . . . >
. . . . . . . .
< Listener className="org.apache.catalina.startup.UserConfig"
directoryName="public_html"
homeBase="C:\TomcatHomes
userClass=
"org.apache.catalina.startup.HomesUserDatabase" / >
. . . . . . . . . . . . < /Host>


In the first scenario, contents for the request to
http://www.myhostingteam.net/~safeer will be
rendered from /home/safeer/public_html where as in second scenario it will be from C:\TomcatHomes\safeer\public_html



Wednesday, December 19, 2007

Tomcat Virtual Hosting

Tomcat virtual hosts are defined inside the Host elements in $CATALINA_HOME\conf\server.xml. $CATALINA_HOME is the tomcat installation directory, hich in my case is /usr/apache-tomcat-5.5.17.
All the Host elements will be enclosed in an "Engine" element. The relevant portion of deafult server.xml is shown below:

<Engine name="Catalina" defaultHost="localhost">
<Realm className="org.apache.catalina.realm.UserDatabaseRealm"
resourceName="UserDatabase"/>
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true"
xmlValidation="false" xmlNamespaceAware="false">
</Host>
</Engine>

Each host element defines a virtual host. To define a virtual host www.myhostingteam.net, we will define a host element as follows.

<Host name="www.myhostingteam.net" appBase="webapps/myhostingteam_net" >
<Context path="" docBase="hostingteam"/>
</Host>

name : The DNS name of the virtual host.

appBase : Application base directory for the virtual host. This directory will hold the web application(s) to be deployed in this virtual host. Path to the directory can be absolute or relative to $CATALINA_HOME.

The "Context" element defines one or more web application contexts defined for this virtual host. There should be atleast one context defined.

path : context path of the web application, which will be matched against the URI request. If left blank, that context will be the default web application for the particular virtual host.

docBase : Document Base/Context Root for the web appliation. This can be either the directiry containing the web application or path to the the web archive (WAR) file for that application. Th e path can be absolute or relative to "appBase" of the virtual host.

Considr the following example:

<Engine name="Catalina" defaultHost="myjavanet.com">

<Host name="www.myjavanet.com" appBase="webapps/myjavanet_com" >
<Context path="" docBase="."/>
</Host>

<Host name="www.myhostingteam.net" appBase="webapps/myhostingteam_net" >
<Context path="" docBase="hostingteam"/>
<Context path="/special" docBase="special_packages"/>
</Host>

</Engine>

Here we have defined two virual hosts, www.myjavanet.com & www.myhostingteam.net

Host www.myjavanet.com: The base directory that holds web applications is /usr/apache-tomcat-5.5.17/webapps/myjavanet_com. There is only one "Context" element, so only one web application is deployed for this host. The "path" attribute is blank indicating that this context is the default context for www.myjavanet.com . The docBAse is ".", means current directory, so the web application is directly deployed inside the appBase Deirectory, which is /usr/apache-tomcat-5.5.17/webapps/myjavanet_com in this case.

Host www.myhostingteam.net : The base directory for web applications is /usr/apache-tomcat-5.5.17/webapps/myhostingteam_net. It holds two web applications, which are deployed in the directories hostingteam and special_packages (docBase) under the "appBase" /usr/apache-tomcat-5.5.17/webapps/myhostingteam_net. The context hostingteam can be accessed with URL http://www.myhostingteam.net, where as special_packages can be accessed with http://www.myhostingteam.net/special (path="/special").

The defaultHost attribute of the Engine element specifies the default virtual host of the tomcat server( if host name in an HTTP request to this server do not match any of the configured virtual host's name attribute, the contents of this virtual host will be supplied instead). This value should match the name attribute of any one of the virtual hosts configured. The default value for this attribute is "localhost".

Multiple DNS names for a single virtual host

Sometimes, you will need to access your site with more than one DNS name, like:
www.myhostingteam.net & hostingteam.net. In such a situation, use the "Alias" element inside the "Host" element.

<Host name="www.myhostingteam.net" ............... >
....................................
<Alias>myhostingteam.net</Alias>
................................
</Host>

You can have any number of <Alias> elements inside a <Host> element.

Monday, December 17, 2007

Running Apache and Tomcat on the same server

Tomcat is a popular servlet container used to host java web applications. By default Tomcat listens on port 8080 (HTTP-Non SSL) and 8443 (HTTP-SSL). If your machine has multiple IPs, Tomcat will automatically bind itself to all the IPs configured.

In a production environement, there are mainly two aproaches to deploy web applications through tomcat. In the first one, the tomcat server will listen on 8080/8443 or any custom ports. All the HTTP request coming to the server (which will default to 80/443) will be received by a web server- most probably "Apache" complied with mod_jk support, or sometimes IIS. All the java related requests will be forwarded to Tomcat. Thus Apache/IIS will act as a proxy for Tomcat. The second approch, which is much more straight forward is to configure tomcat to listen on port 80/443.

With the second aproach, Tomcat will listen on port 80/443 on all configured IP addresses of the server. This is ok if you have a dedicated server for running tomcat. But it may not be the case always. In my case, when I was asked to set up Tomcat(to listen on 80/443) on one of our live servers, it was already running an apache server that serve a bunch of production web sites.

I had two choices now, either to buy a new server for hosting tomcat or to configure both apache and tomcat on the same server. I would have chosen the first option for perfomance reasons, but it was costly. So I decided to go for the later, and bought one additional public IP for the current production server.

I reconfigured apache to listen on one IP address. For this I edited

/etc/httpd/conf/httpd.conf file to change the entry
Listen 80
TO
Listen 61.17.42.50:80

AND
/etc/httpd/conf.d/ssl.conf to change the entry
Listen 443
TO
Listen 61.17.42.50:443

Now apache will listen on the IP 61.17.42.50 only.

Then I configured Tomcat to listen on the newly prcuhased IP, 61.17.42.76

The configuration file for tomcat is CATALINA_HOME/conf/server.xml where CATALINA_HOME is the installation directory for tomcat. In my case it is
/usr/apache-tomcat-5.5.17.

The file server.xml consists of a number of 'elements' enclosed in ankle brackets and a number of attributes specified for each element within those brackets.

The element that is of interest to us is "connector". There is one connector elements each for HTTP-Non-SSL and HTTP-SSL. I will quote the relevant portions of the configuration file:

HTTP Non SSL

<Connector port="8080" maxHttpHeaderSize="8192"
maxThreads="150" minSpareThreads="25" maxSpareThreads="75"
enableLookups="false" redirectPort="8443" acceptCount="100"
connectionTimeout="20000" disableUploadTimeout="true" />

HTTP SSL

<Connector port="8443" maxHttpHeaderSize="8192"
maxThreads="150" minSpareThreads="25" maxSpareThreads="75"
enableLookups="false" disableUploadTimeout="true"
acceptCount="100" scheme="https" secure="true"
clientAuth="false" sslProtocol="TLS" />

First, change the ports to listen on 80 and 443

<Connector port="80" maxHttpHeaderSize="8192"
maxThreads="150" minSpareThreads="25" maxSpareThreads="75"
enableLookups="false" redirectPort="443" acceptCount="100"
connectionTimeout="20000" disableUploadTimeout="true" />

HTTP SSL

<Connector port="443" maxHttpHeaderSize="8192"
maxThreads="150" minSpareThreads="25" maxSpareThreads="75"
enableLookups="false" disableUploadTimeout="true"
acceptCount="100" scheme="https" secure="true"
clientAuth="false" sslProtocol="TLS" />

This is not enough, because tomcat will now try to bind on ports 80 & 443 on both the public IPs. To limit tomcat to listen on 61.17.42.76 only, add an attribute "address" to both the connector elements.

<Connector port="80" maxHttpHeaderSize="8192" address="61.17.42.76"
maxThreads="150" minSpareThreads="25" maxSpareThreads="75"
enableLookups="false" redirectPort="443" acceptCount="100"
connectionTimeout="20000" disableUploadTimeout="true" />

HTTP SSL

<Connector port="443" maxHttpHeaderSize="8192" address="61.17.42.76"
maxThreads="150" minSpareThreads="25" maxSpareThreads="75"
enableLookups="false" disableUploadTimeout="true"
acceptCount="100" scheme="https" secure="true"
clientAuth="false" sslProtocol="TLS" />

It is not important in what order you arrange the attributes inside an element, but leave spaces between each attributes.

Now that I have apache and tomcat running on the 80/443, I can configure the DNS records for the web sites hosted in apache to point to 61.17.42.50 and DNS record for web sites to be served by tomcat to point to 61.17.42.76.