April 3, 2009

Plone and Apache on FreeBSD 7 behind PF

Plone 3.2.1 and Apache 2.2 on FreeBSD 7.2 behind PF

Several years have passed since I published a paper on how to configure IP Filter on OpenBSD 2.8.  At the time, PF was not yet integrated into the OpenBSD kernel.  OpenBSD 3.0 soon prevailed (2001) and PF was included in the kernel.  Soon thereafter (2003), we saw the incorporation of PF into the FreeBSD 5.3 kernel.   For those who are unfamiliar, PF is a system for filtering TCP/IP traffic and providing network address translation. However; PF also provides network traffic shaping capabilities - packet prioritization, bandwidth control, and TCP/IP conditioning.   My original article from 2000 explained how to setup an IP-less bridge on an OpenBSD 2.8 server running IP Filter  with dual network interface cards. The bridge filtered traffic at the data link layer and was invisible at the internet protocol level.   As you will see, PF, like IP Filter, is very powerful.  While I will not be going into how to configure an IP-less bridge, the PF configuration that follows is straightforward and easily adaptable to your configuration.

This document shows an example configuration of a PF ruleset and an Apache 2.2 installation in front of a Plone 3.2.1 instance on FreeBSD 7.2.  SSH tunnelling is used for remote management of the Zope/Plone instance (i.e. ZMI).

Pre-installation Requirements

  • FreeBSD 7.2-PRERELEASE w/ PF enabled
  • Apache 2.2 from (from ports) with mod_ssl (OpenSSL 0.9.8j), mod_proxy, and mod_rewrite
  • Zope w/ Plone 3.2.1
  • Varnish HTTP Accelerator

Zope is bound to an unprivileged port on localhost.  Apache is bound as a non-privileged user to port 80 and 443 on public IP address X.X.X.Y.  Zope/Plone can be running standalone or in a ZEO Server / ZEO client configuration.  In either case, Apache will function as a reverse proxy and send http requests to Zope.

Enable IP Forwarding in the Kernel

If you have not already done so, enable IP forwarding in the kernel:

# sysctl net.inet.ip.forwarding=1
# sysctl net.inet.ip.fastforwarding=1
# sysctl net.inet6.ip6.forwarding=1

Add the following lines to /etc/sysctl.conf so that when you reboot, IP forwarding is enabled:

# /etc/sysctl.conf

net.inet.ip.forwarding=1
net.inet.ip.fastforwarding=1
net.inet6.ip6.forwarding=1
kern.ipc.somaxconn=4096
kern.ipc.nmbclusters=32768

As an alternative, you can add the following to /etc/rc.conf

# /etc/rc.conf

gateway_enable="YES"

 

Enable HTTP Accept Filter

Next, make sure that the HTTP Accept filter is loaded into the kernel.

You can check this by running the following command:

# kldstat

2    1 0xc0b12000 2464     accf_http.ko

If the filter is not loaded, edit /boot/loader.conf and add the following line so that when you reboot, the HTTP Accept filter kernel module is loaded.

# /boot/loader.conf
 accf_http_load="YES"

Last of all, to load the module without rebooting, run the following command:

kldload accf_http

 

System V Shared Memory and Semaphore Parameters

Modify System V shared memory and semaphore parameters

# sysctl kern.ipc.shmall=32768
# sysctl kern.ipc.shmmax=134217728
# sysctl kern.ipc.semmap=256

Make these changes permanent by adding the following to /etc/sysctl.conf

# /etc/sysctl.conf

kern.ipc.shmall=32768
kern.ipc.shmmax=134217728
kern.ipc.semmap=256
net.inet.ip.forwarding=1
net.inet.ip.fastforwarding=1
net.inet6.ip6.forwarding=0
kern.ipc.somaxconn=4096
kern.ipc.nmbclusters=32768

 

System V "Read-Only" Semaphore Parameters

Modify the System V "Read-Only" Semaphore Parameters by adding the following to /boot/loader.conf  

NOTE: You must reboot for the new values of these parameters to take effect

# /boot/loader.conf

kern.ipc.semmni=256
kern.ipc.semmns=512
kern.ipc.semmnu=256
accf_http_load="YES"
net.inet.tcp.syncache.hashsize=1024
net.inet.tcp.syncache.bucketlimit=100
net.inet.tcp.tcbhashsize=4096
net.inet.tcp.syncache.cachelimit=102400

 

PF Configuration

# /etc/pf.conf
#

## MACROS-----
ext_if="bge0"
set loginterface $ext_if
local_networks = "{ a.a.a.b/24, c.c.c.d/24, e.e.e.f/26}"
internet_ports = "{80, 443}"

# Table Setup
# /etc/iface_addresses contains the following
# X.X.X.Y
# X.X.X.Z
table <iface_addresses> persist file "/etc/iface_addresses"
table <bruteforce> persist

# set Block Policy option
set block-policy return

# set Skip Filtering option on localhost
set skip on lo0

scrub in all
antispoof quick for $ext_if inet

# block ip addresses contained in bruteforce table
block in log (all, to pflog0) quick on $ext_if from <bruteforce> to any

# block and then log outgoing packets that don't have one of our IPs as the source IP
block out log (all, to pflog0) quick on $ext_if from ! <iface_addresses> to any

# block nmap scans
block in log (all, to pflog0) quick on $ext_if inet proto { tcp, udp } from any to any flags FUP/FUP

# block everything by default
block in on $ext_if all

# pass in icmp and keep state
pass in quick on $ext_if inet proto icmp all keep state

# pass in tcp traffic from localhost
pass in quick on $ext_if proto tcp from 127.0.0.1 to <iface_addresses>

# pass in traffic on internet ports
pass in on $ext_if proto { tcp, udp } from any to <iface_addresses> port $internet_ports flags S/SA keep state

# throttle ssh connection attempts and block their ip if a bruteforce attempt is detected
pass in quick on $ext_if proto tcp from any to any port ssh \
     flags S/SA keep state \
     (max-src-conn 15, max-src-conn-rate 5/3, \
      overload <bruteforce> flush global)

# allow traffic from interface addresses to localhost
pass out quick on $ext_if from <interfaces_addresses> to 127.0.0.1

# allow local network admin ip addresses
pass in on $ext_if proto { tcp, udp } from $local_networks to $ext_if

# keep state on outbound connections made from one of the ip addresses on interface
pass out on $ext_if proto tcp from any to any flags S/SA modulate state
pass out on $ext_if proto { udp, icmp } from any to any keep state


 Apache Configuration

# /usr/local/etc/apache22/httpd.conf
# ---------------------------------- 


Listen X.X.X.Y:80 # Default
Listen X.X.X.Z:80 # VHost
Listen X.X.X.Z:80:443 # VHost

HTTP Virtual Hosts

# /usr/local/etc/apache22/Includes/httpd-vhosts.conf
# --------------------------------------------------
NameVirtualHost X.X.X.Z:80

<VirtualHost X.X.X.Z:80>
   ServerName DOMAIN.com
   ServerAlias www.DOMAIN.com
   ServerAdmin info@DOMAIN.com
   ServerSignature On
   RequestHeader set Front-End-Https "On"
   ProxyRequests Off
   ProxyPreserveHost On

   ErrorLog "/var/log/DOMAIN-error_log"
   CustomLog "/var/log/DOMAIN-access_log" common
   LogLevel warn

   <IfModule mod_rewrite.c>
      RewriteEngine On
      RewriteLogLevel 2
      RewriteRule ^/icons/ - [L]
      RewriteRule ^/(.*)/manage(.*) \
      https://DOMAIN.com/$1/manage$2 [NC,R=301,L]
      RewriteRule ^/manage(.*) \
      https://DOMAIN.com/manage$1 [NC,R=301,L]
      RewriteRule ^/login_(.*) https://%{SERVER_NAME}/login_$1 [NE,L]
      RewriteRule ^/(.*) \
         http://127.0.0.1:8902/VirtualHostBase/http/%{SERVER_NAME}:80/MyPloneSite/VirtualHostRoot/$1 [P,L]
    </IfModule>
    <IfModule mod_proxy.c>
       ProxyVia On
       ProxyPass / http://127.0.0.1:8902/VirtualHostBase/http/%{SERVER_NAME}:80/MyPloneSite/VirtualHostRoot/
       ProxyPassReverse / http://127.0.0.1:8902/VirtualHostBase/http/%{SERVER_NAME}:80/MyPloneSite/VirtualHostRoot/
       <ProxyMatch http://127.0.0.1:*/.* >
          Order deny,allow
          Deny from all
          Allow from X.X.X.Y
       </ProxyMatch>
       <LocationMatch "^[^/]">
          Deny from all
       </LocationMatch>
    </IfModule>
</VirtualHost>

SSL Virtual Hosts

# /usr/local/etc/apache22/Includes/httpd-ssl.conf

NameVirtualHost X.X.X.Z:443

<VirtualHost X.X.X.Z:443>
   DocumentRoot "/usr/local/www/apache22/data"
   ServerName DOMAIN.com
   ServerAdmin info@DOMAIN.com
   ErrorLog "/var/log/DOMAIN-ssl-error.log"
   TransferLog "/var/log/DOMAIN-ssl-access.log"

   SSLEngine on
   SSLCipherSuite ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+SSLv3:+EXP:+eNULL
   SSLCertificateFile "/usr/local/etc/apache22/ssl.crt/DOMAIN.com.crt"
   SSLCertificateKeyFile "/usr/local/etc/apache22/ssl.key/DOMAIN.com.key"

   <FilesMatch "\.(cgi|shtml|phtml|php)$">
      SSLOptions +StdEnvVars
   </FilesMatch>
   <Directory "/usr/local/www/apache22/cgi-bin">
      SSLOptions +StdEnvVars
   </Directory>
   BrowserMatch ".*MSIE.*" \
   nokeepalive ssl-unclean-shutdown \
   downgrade-1.0 force-response-1.0

   CustomLog "/var/log/cfhinton-ssl_request.log" \
          "%t %h %{SSL_PROTOCOL}x %{SSL_CIPHER}x \"%r\" %b"

    <IfModule mod_rewrite.c>
      RewriteEngine On
      RewriteLogLevel 2

      RewriteRule ^/(.*) \
         http://127.0.0.1:8902/VirtualHostBase/https/%{SERVER_NAME}:443/MyPloneSite/VirtualHostRoot/$1 [L,P]
    </IfModule>
    <IfModule mod_proxy.c>
       ProxyVia On
       ProxyPass / http://127.0.0.1:8902/VirtualHostBase/http/%{SERVER_NAME}:80/MyPloneSite/VirtualHostRoot/
       ProxyPassReverse / http://127.0.0.1:8902/VirtualHostBase/http/%{SERVER_NAME}:80/MyPloneSite/VirtualHostRoot/
       <ProxyMatch http://127.0.0.1:*/.* >
          Order deny,allow
          Deny from all
          Allow from X.X.X.Y
       </ProxyMatch>
       <LocationMatch "^[^/]">
          Deny from all
       </LocationMatch>
    </IfModule>
</VirtualHost>

SSH Tunnel (access ZMI via http://localhost:9999

ssh -f user@mydomain.com -L 9999:localhost:8095 -N