HOWTO to setup CPS with Apache httpd VirtualHost directives

Author: Marc-Aurèle Darche
Revision: $Id$

Contents

1   Introduction

1.1   Apache httpd different flavors

This document explains how to setup CPS behind the Apache httpd server through the reverse proxy technique.

This kind of configuration is the preferred way to deploy CPS web sites because:

  • Apache httpd is very fast and can handle a web cache.
  • Apache httpd supports the ability to have parts of CPS web sites protected through HTTPS.
  • Apache httpd supports the ability to combine many web sites and many technologies together (CPS, Zope, PHP, CGI, Perl, Java, etc.) together behind a single domain name.

The Apache httpd server comes in different series (the 1.3.x and the 2.x series, etc.) and also in different versions (the standard httpd version and the Apache-SSL flavor).

In this document we will only explain the use of the following versions:

  • Apache httpd 2.x (usually called Apache2)
  • Apache-ssl

Using Apache 2 is the preferred option because it is the more up-to-date version and the version on which development is done. Apache-ssl was only handy before Apache 2. But now that Apache 2 ships with mod_ssl by default, there isn't' any reason to stay with Apache-SSL anymore.

1.2   Adapting the examples to your needs

This howto presents configurations for Debian 3.1 "Sarge" systems and should work on any Debian based systems (Ubuntu, etc.).

Port 9673 is the Zope default port on Debian. You might have to change it to 8080 depending on your configuration.

In the following examples machine.localdomain can be replaced by localhost if your Zope server runs on the same machine as your Apache httpd server.

Finally note that while this howto focuses on CPS, the most complete Open Source solution available for building Enterprise Content Management (ECM) applications, it could advantageously be followed for other Zope-based applications such as Plone.

2   Using Apache 2

Here are some configuration examples using Apache2 httpd VirtualHost directives.

2.1   Prerequisites

What you need:

  1. $ apt-get install apache2
    
  2. Enable the following modules: proxy, rewrite, ssl

    On a Debian system it is done by calling the commands:

    $ a2enmod proxy
    $ a2enmod rewrite
    $ a2enmod ssl
    

    On a Debian system with Apache 2.2 (typically Debian Etch) you might get the following error message:

    proxy: No protocol handler was valid for the URL /. If you are using a DSO version of mod_proxy, make sure the proxy submodules are included in the configuration using LoadModule.
    

    In this case the following module should also be enabled:

    $ a2enmod proxy_http
    
  3. Open the needed ports in /etc/apache2/ports.conf:

    Listen 80
    Listen 443
    Listen 453
    
  4. Authorize proxy requests in /etc/apache2/mods-enabled/proxy.conf otherwise you could end up with an unreachable CPS web site and messages like client denied by server configuration: proxy:http://localhost in your log files:

    <Proxy *>
      Order deny,allow
      Deny from all
    </Proxy>
    
    <Proxy http://localhost:9673>
      Order deny,allow
      Deny from all
      Allow from all
    </Proxy>
    
  5. If you want to use HTTPS for your web server you should either:

    • generate a single self-signed certificate (this is the easy way to go if you just want HTTPS for one portal and don't care about flexibility, evolution, multiple service or domain names on the same machine, etc.)
    • generate a private key and certificate file (this is the more serious and flexible way to go)

    We will only document the single self-signed certificate generation procedure that can easily be use on Debian systems. This documentation is not about teaching you how to manipulate certificates or installing/using a PKI.

    On Debian-based systems there is a small utility that can generate the self-signed certificate for you: apache2-ssl-certificate:

    $ /usr/sbin/apache2-ssl-certificate
    

    Just answer the few questions (Country Name, server name, Email Address, etc.) about your service name or portal name and machine and it will generate the certificate for you in /etc/apache2/ssl/apache.pem.

    If you are not satisfied with the questions asked by the utility (for example to get rid of the stupid State or Province Name information and the localityName information) edit the OpenSSL configuration file used by Apache before running the utility:

    [ req_distinguished_name ]
    countryName                     = Country Name (2 letter code)
    # You can change the default values
    #countryName_default             = GB
    countryName_default             = FR
    countryName_min                 = 2
    countryName_max                 = 2
    
    # Just comment out the option you don't want to have to be set
    #stateOrProvinceName             = State or Province Name (full name)
    #stateOrProvinceName_default     = Some-State
    
    # Just comment out the option you don't want to have to be set
    #localityName                    = Locality Name (eg, city)
    
    organizationName                = Organization Name (eg, company; recommended)
    organizationName_max            = 64
    
    organizationalUnitName          = Organizational Unit Name (eg, section)
    organizationalUnitName_max      = 64
    
    commonName                      = server name (eg. ssl.domain.tld; required!!!)
    commonName_max                  = 64
    
    emailAddress                    = Email Address
    emailAddress_max                = 40
    

    Then you can check the information that ended in the certificate:

    $ openssl x509 -in /etc/apache2/ssl/apache.pem -text
    
    Certificate:
        Data:
            Version: 1 (0x0)
            Serial Number:
                a0:35:f0:c7:d1:68:5a:27
            Signature Algorithm: md5WithRSAEncryption
            Issuer: C=FR, O=MySite, CN=www.mysite.net/emailAddress=webmaster@mysite.net
            Validity
                Not Before: May 18 13:15:45 2006 GMT
                Not After : Jun 17 13:15:45 2006 GMT
            Subject: C=FR, O=MySite, CN=www.mysite.net/emailAddress=webmaster@mysite.net
            Subject Public Key Info:
                Public Key Algorithm: rsaEncryption
                RSA Public Key: (1024 bit)
                    Modulus (1024 bit):
                        00:cb:4c:6e:69:91:b4:70:d2:55:80:15:fe:34:e9:
                        85:df:74:56:6a:6c:de:15:f6:b3:ba:78:b8:06:74:
                        b4:d3:c6:35:cf:6c:8d:21:7b:53:0e:b1:c9:24:51:
                        bc:23:9f:bd:c5:b1:07:5a:30:34:5a:97:e8:4c:d5:
                        5f:83:24:7e:3b:d9:9d:07:bd:d3:ca:4d:a4:f7:4b:
                        d2:49:c2:63:6d:4e:3e:82:58:91:b6:45:2f:80:61:
                        c2:a1:6e:10:e8:1d:21:b7:f9:e2:0e:b6:95:24:dd:
                        ae:82:9c:6c:3e:38:ac:ca:cb:e2:74:fc:65:97:85:
                        40:39:3d:ee:81:16:db:57:8f
                    Exponent: 65537 (0x10001)
        Signature Algorithm: md5WithRSAEncryption
            5a:6e:6e:b0:82:aa:b6:71:42:24:b8:d5:31:6a:78:13:81:a2:
            dc:c3:91:91:e5:20:46:b5:91:81:11:f6:bc:86:4e:e2:a5:bd:
            d9:b8:c1:ca:16:a1:46:de:4e:69:bf:7a:dd:5e:24:dd:d6:53:
            12:12:23:75:bd:e2:45:ad:81:7f:8f:82:35:20:ce:68:69:71:
            50:ea:45:8f:4b:fe:f4:be:84:53:4d:2b:7d:85:5b:bd:0d:8f:
            6b:66:2a:87:9e:41:94:ee:44:01:ae:76:45:ad:e9:a1:71:fd:
            6f:1d:96:d3:53:66:d1:a7:96:97:54:ac:43:b1:78:77:90:a1:
            ac:aa
    -----BEGIN CERTIFICATE-----
    GhxeGTCCAaYCCQCgNfDH0WhaJzANBgkqhkiG9w0BAQQFADBjMQswCQYDVQQGEwJG
    UjEOMAwGA1UEChMFTW9udW0xHjAcBgNVBAMTFXd3dy5pbnRyYW5ldC5tb251bS5m
    cjEkMCIGCSqGSIb3DQEJARYVY29tLmludHJhbmV0QG1vbnVtLmZyMB4XDTA2MDUx
    ODEzMTU0NVoXDTA2MDYxNzEzMTU0NVowYzELMAkGA1UEBhMCRlIxDjAMBgNVBAoT
    BU1vbnVtMR4wHAYDVQQDExV3d3ergeg,melrGERGRG9udW0uZnIxJDAiBgkqhkiG
    9w0BCQEWFWNvbS5pbnRyYW5ldEBtb251bS5mcjCBnzANBgkqhkiG9w0BAQEFAAOB
    jQAwgYkCgYEAy0xuaZG0cNJVgBX+NOmF33RWamzeFfazuni4BnS008Y1z2yNIXtT
    DrHJJFG8I5+9xbEHWjA0WpfoTNVfgyR+O9mdB73Tyk2k90vSScJjbU4+gliRtkUv
    gGHCoW4Q6B0ht/niDraVJN2ugpxsPjisysvidPxll4VAOT3ugRbbV48CAwEAATAN
    BgkqhkiG9w0BAQQFAAOBgQBabm6wgqq2cUIkuNUxangTgaLcw5GR5SBGtZGBEfa8
    hk7ipb3ZuMHKFqFG3k5pv3rdXiTd1lMSEiN1veJFrYF/j4I1IM5oaXFQ6kWPS/70
    voRTTSt9hVu9DY9rZiqHnkGU7kQBrnZFremhcf1vHZbTU2bRp5aXVKxDsXgrA3Gs
    qg==
    -----END CERTIFICATE-----
    

2.2   Simple virtual host HTTP + HTTPS configuration

This configuration is what most people would need. This is not a secure configuration, but it is easy to setup and understand.

Example:

<VirtualHost 192.168.2.20:80>
ServerName www.mysite.net

RewriteEngine on

RewriteCond %{HTTP:Authorization}  ^(.*)
RewriteRule ^/(.*) http://machine.localdomain:9673/VirtualHostBase/http/%{HTTP_HOST}:80/cps/VirtualHostRoot/$1 [P,L]

CustomLog /var/log/apache2/www.mysite.net.log combined
ErrorLog /var/log/apache2/www.mysite.net-error.log
</VirtualHost>

<VirtualHost 192.168.2.20:443>
ServerName www.mysite.net

SSLEngine on
SSLCertificateFile /etc/apache2/ssl/apache.pem
# Alternatively use those lines for private key and certificate configurations
#SSLCertificateFile /etc/apache2/ssl/www.mysite.net.cert
#SSLCertificateKeyFile /etc/apache2/ssl/www.mysite.net.key

RewriteEngine on

RewriteCond %{HTTP:Authorization}  ^(.*)
RewriteRule ^/(.*)  http://machine.localdomain:9673/VirtualHostBase/https/%{HTTP_HOST}:443/cps/VirtualHostRoot/$1 [P,L]

CustomLog /var/log/apache2/www.mysite.net.log combined
ErrorLog /var/log/apache2/www.mysite.net-error.log
</VirtualHost>

2.3   Secure virtual host HTTP + HTTPS configuration

This is a secure configuration because:

  • It forces the use of HTTPS for administering Zope in the ZMI.
  • It forces the use of HTTPS for authenticated users (because for logged users cookies containing vulnerable login/password information is sent with each request).
  • It forces the use of HTTPS for users who wish to join the portal (because login information is provided in the join form).

Example:

# Main HTTP access to http://www.mysite.net/ for anonymous users
<VirtualHost 192.168.2.20:80>
ServerName www.mysite.net

RewriteEngine on

# Using OR instead of the implicit AND between conditions
RewriteCond %{REQUEST_URI} ^(.*)/manage(.*) [OR]
RewriteCond %{REQUEST_URI} ^(.*)/login(.*) [OR]
RewriteCond %{REQUEST_URI} ^(.*)/account_(.*) [OR]
RewriteCond %{REQUEST_URI} ^(.*)/join_form$
RewriteRule ^/(.*) https://www.mysite.net/$1 [R=permanent,L]

RewriteCond %{HTTP:Authorization}  ^(.*)
RewriteRule ^/(.*) http://machine.localdomain:9673/VirtualHostBase/http/%{HTTP_HOST}:80/cps/VirtualHostRoot/$1 [P,L]

CustomLog /var/log/apache2/www.mysite.net.log combined
ErrorLog /var/log/apache2/www.mysite.net-error.log
</VirtualHost>

# Main HTTPS access to https://www.mysite.net/ for authenticated users
<VirtualHost 192.168.2.20:443>
ServerName www.mysite.net

SSLEngine on
SSLCertificateFile /etc/apache2/ssl/apache.pem
# Alternatively use those lines for private key and certificate configurations
#SSLCertificateFile /etc/apache2/ssl/www.mysite.net.cert
#SSLCertificateKeyFile /etc/apache2/ssl/www.mysite.net.key

RewriteEngine on

RewriteCond %{HTTP:Authorization}  ^(.*)
RewriteRule ^/(.*)  http://machine.localdomain:9673/VirtualHostBase/https/%{HTTP_HOST}:443/cps/VirtualHostRoot/$1 [P,L]

CustomLog /var/log/apache2/www.mysite.net.log combined
ErrorLog /var/log/apache2/www.mysite.net-error.log
</VirtualHost>


# HTTPS access to https://www.mysite.net:453/ for administrators.
# This is the access to use to administer Zope through the ZMI.
<VirtualHost 192.168.2.20:453>
ServerName www.mysite.net

SSLEngine on
SSLCertificateFile /etc/apache2/ssl/apache.pem
# Alternatively use those lines for private key and certificate configurations
#SSLCertificateFile /etc/apache2/ssl/www.mysite.net.cert
#SSLCertificateKeyFile /etc/apache2/ssl/www.mysite.net.key

RewriteEngine on

RewriteCond %{HTTP:Authorization}  ^(.*)
RewriteRule ^/(.*)  http://machine.localdomain:9673/VirtualHostBase/https/%{HTTP_HOST}/VirtualHostRoot/$1 [P,L]
# Note that the line below with "%{HTTP_HOST}:453" will not work. The working
# rule above has been crafted through the reading of the Z2.log file.
#RewriteRule ^/(.*) http://machine.localdomain:9673/VirtualHostBase/https/%{HTTP_HOST}:453/VirtualHostRoot/$1 [P,L]

CustomLog /var/log/apache2/www.mysite.net.log combined
ErrorLog /var/log/apache2/www.mysite.net-error.log
</VirtualHost>

3   Using Apache (apache-ssl package)

Here are some configuration examples using Apache-SSL VirtualHost directives.

Note that those configuration instructions are "apache-ssl" specific. It is of course possible to use the "apache" and "libapache-mod-ssl" packages, instead of using the "apache-ssl" package, but the configuration might be slightly different.

3.1   Prerequisites

What you need:

  1. $ apt-get install apache-ssl
    
  2. Be sure to have the following line in your /etc/apache-ssl/modules.conf:

    LoadModule proxy_module /usr/lib/apache/1.3/libproxy.so
    
  3. You should have the SSLDisable option at the server configuration level because we will be using virtual hosts.

  4. You should generate a private key and certificate files for your web server.

3.2   Simple virtual host HTTP + HTTPS configuration

This configuration is what most people would need. This is not a secure configuration but it is easy to setup and understand.

Example:

<VirtualHost 192.168.2.20:80>
ServerName www.mysite.net

RewriteEngine on

RewriteCond %{HTTP:Authorization}  ^(.*)
RewriteRule ^/(.*) http://machine.localdomain:9673/VirtualHostBase/http/%{HTTP_HOST}:80/cps/VirtualHostRoot/$1 [P,L]

CustomLog /var/log/apache-ssl/www.mysite.net.log combined
ErrorLog /var/log/apache-ssl/www.mysite.net-error.log
</VirtualHost>

<VirtualHost 192.168.2.20:443>
ServerName www.mysite.net

SSLEnable
SSLCertificateFile /etc/apache-ssl/ssl.crt/apache.pem
# Alternatively use those lines for private key and certificate configurations
SSLCertificateFile /etc/apache-ssl/ssl.crt/www.mysite.net.cert
SSLCertificateKeyFile /etc/apache-ssl/ssl.key/www.mysite.net.key

RewriteEngine on

RewriteCond %{HTTP:Authorization}  ^(.*)
RewriteRule ^/(.*)  http://machine.localdomain:9673/VirtualHostBase/https/%{HTTP_HOST}:443/cps/VirtualHostRoot/$1 [P,L]

CustomLog /var/log/apache-ssl/www.mysite.net.log combined
ErrorLog /var/log/apache-ssl/www.mysite.net-error.log
</VirtualHost>

3.3   Secure virtual host HTTP + HTTPS configuration

This is a secure configuration because:

  • It forces the use of HTTPS for administering Zope in the ZMI.
  • It forces the use of HTTPS for authenticated users (because for logged users cookies containing vulnerable login/password information is sent with each request).
  • It forces the use of HTTPS for users who wish to join the portal (because login information is provided in the join form).

Example:

# Main HTTP access to http://www.mysite.net/ for anonymous users
<VirtualHost 192.168.2.20:80>
ServerName www.mysite.net

RewriteEngine on

# Using OR instead of the implicit AND between conditions
RewriteCond %{REQUEST_URI} ^(.*)/manage(.*) [OR]
RewriteCond %{REQUEST_URI} ^(.*)/login(.*) [OR]
RewriteCond %{REQUEST_URI} ^(.*)/account_(.*) [OR]
RewriteCond %{REQUEST_URI} ^(.*)/join_form$
RewriteRule ^/(.*) https://www.mysite.net/$1 [R=permanent,L]

RewriteCond %{HTTP:Authorization}  ^(.*)
RewriteRule ^/(.*) http://machine.localdomain:9673/VirtualHostBase/http/%{HTTP_HOST}:80/cps/VirtualHostRoot/$1 [P,L]

CustomLog /var/log/apache-ssl/www.mysite.net.log combined
ErrorLog /var/log/apache-ssl/www.mysite.net-error.log
</VirtualHost>

# Main HTTPS access to https://www.mysite.net/ for authenticated users
<VirtualHost 192.168.2.20:443>
ServerName www.mysite.net

SSLEnable
SSLCertificateFile /etc/apache-ssl/ssl.crt/apache.pem
# Alternatively use those lines for private key and certificate configurations
SSLCertificateFile /etc/apache-ssl/ssl.crt/www.mysite.net.cert
SSLCertificateKeyFile /etc/apache-ssl/ssl.key/www.mysite.net.key

RewriteEngine on

RewriteCond %{HTTP:Authorization}  ^(.*)
RewriteRule ^/(.*)  http://machine.localdomain:9673/VirtualHostBase/https/%{HTTP_HOST}:443/cps/VirtualHostRoot/$1 [P,L]

CustomLog /var/log/apache-ssl/www.mysite.net.log combined
ErrorLog /var/log/apache-ssl/www.mysite.net-error.log
</VirtualHost>


# HTTPS access to https://www.mysite.net:453/ for administrators.
# This is the access to use to administer Zope through the ZMI.
<VirtualHost 192.168.2.20:453>
ServerName www.mysite.net

SSLEnable
SSLCertificateFile /etc/apache-ssl/ssl.crt/www.mysite.net.cert
SSLCertificateKeyFile /etc/apache-ssl/ssl.key/www.mysite.net.key

RewriteEngine on

RewriteCond %{HTTP:Authorization}  ^(.*)
RewriteRule ^/(.*)  http://machine.localdomain:9673/VirtualHostBase/https/%{HTTP_HOST}/VirtualHostRoot/$1 [P,L]
# Note that the line below with "%{HTTP_HOST}:453" will not work. The working
# rule above has been crafted through the reading of the Z2.log file.
#RewriteRule ^/(.*) http://machine.localdomain:9673/VirtualHostBase/https/%{HTTP_HOST}:453/VirtualHostRoot/$1 [P,L]

CustomLog /var/log/apache-ssl/www.mysite.net.log combined
ErrorLog /var/log/apache-ssl/www.mysite.net-error.log
</VirtualHost>

4   Hiding "sections" in the URL

CPS has its contents both in workspaces and sections folders.

While being aware of those two locations is fine for users collaborating on a CPS portal (and actually producing contents under the workspaces folder), it is disturbing and useless to have sections in the URL of contents publicly available and accessed by anonymous users.

A possible solution is to hide the sections part by adding yet another RewriteRule for each public rubric:

RewriteCond %{REQUEST_URI} ^/rubric-1
RewriteRule ^/(.*) http://machine.localdomain:9673/VirtualHostBase/http/%{HTTP_HOST}:80/cps/sections/VirtualHostRoot/$1 [P,L]

RewriteCond %{REQUEST_URI} ^/rubric-2
RewriteRule ^/(.*) http://machine.localdomain:9673/VirtualHostBase/http/%{HTTP_HOST}:80/cps/sections/VirtualHostRoot/$1 [P,L]

etc.

Note that those rules should appear before the generic proxying rule defined earlier:

RewriteRule ^/(.*) http://machine.localdomain:9673/VirtualHostBase/http/%{HTTP_HOST}:80/cps/VirtualHostRoot/$1 [P,L]

Note that those rules will not hide the true locations of contents as returned by the search pages or the navigation portlets. To this end one usually needs to define custom navigation portlets.

5   Using a web cache

If a CPS site is heavily loaded by frequent visits it's a good idea to put it behind a web cache.

The preferred solutions are to use either Apache httpd or Varnish.

5.1   With Apache httpd

From version 2.0 Apache httpd comes with a web cache. But only with Apache httpd version >= 2.2 is this web cache really efficient.

Add this fragment to your Apache httpd virtual host configuration file:

<IfModule mod_disk_cache.c>
  # Enable caching of specified URLs using a specified storage manager
  CacheEnable disk /
  # This directory must exist
  CacheRoot /var/cache/apache2/www.mysite.net
  # 500Mb of space used on the filesystem
  CacheSize 512000
  # Number of characters for each subdirectory name in the cache hierarchy.
  # This is to try to reduce the number of subdirectories.
  CacheDirLength 5
  # Ignore the fact that the client requested the content not be cached
  CacheIgnoreCacheControl On
</IfModule>

5.2   With Varnish

For Varnish, use version 1.1.1 or up.

Use the following VCL configuration file:

backend default {
        set backend.host = "127.0.0.1";
        set backend.port = "8080";
}

acl purge {
        "localhost";
        "192.0.2.0"/24;
}

sub vcl_recv {
        if (req.request != "GET" && req.request != "HEAD") {
                # PURGE request if zope asks nicely
                if (req.request == "PURGE") {
                        if (!client.ip ~ purge) {
                                error 405 "Not allowed.";
                        }
                        lookup;
                }
                pipe;
        }
        if (req.http.Expect) {
                pipe;
        }
        if (req.http.Authenticate || req.http.Authorization) {
                pass;
        }
        # We only care about the "__ac.*" cookies, used for authentication
        if (req.http.Cookie && req.http.Cookie ~ "__ac(|_(name|password|persistent))=") {
                pass;
        }
        lookup;
}

# Do the PURGE thing
sub vcl_hit {
        if (req.request == "PURGE") {
                set obj.ttl = 0s;
                error 200 "Purged";
        }
}
sub vcl_miss {
        if (req.request == "PURGE") {
                error 404 "Not in cache";
        }
}

# Enforce a minimum TTL, since we PURGE changed objects actively from Zope.
sub vcl_fetch {
        if (obj.ttl < 3600s) {
                set obj.ttl = 3600s;
        }
}

6   Using a load balancer

The specific case of using a load balancer is addressed by the CPSApacheProxyBalancer product and documented there.

7   Developer information