Skip to main content

iptables port forwarding using puppet

In this guide, I show how to write Puppet resource configuration for adding an iptables rule for forwarding traffic between two ports (such as from Port 80 to Port 8080). Although this technique could serve a number of purposes, I use it principally for running an application (such as a Ruby or Clojure web application, perhaps using Ruby on Rails or Compojure) on a non-privileged port. I assume a Debian-variant OS (such as Debian itself or Ubuntu Server).

In order to not be completely weird (although I admit I did consider it), I want to expose my web applications on the usual Port 80, so it can be easily reached via a browser. If you’re running Nginx or even Apache as a service, this is all well and good; the service is started with root privileges, and Port 80 requests can be handled. But what if you’re running a standalone server like Thin, packaged with your application? In that case, you might have a Procfile looking something like this for Ruby (this example’s Ruby on Rails):

web: bundle exec rails server -p $PORT

or like this for Clojure (if you’re packaging your application into an uberjar):

web: java -jar target/*-*-standalone.jar $PORT

Setting the environment variable PORT=80 will likely yield tears, however, because of course you’re not running your application with root privileges (if you are, please don’t tell me because I shall probably worry on your behalf). Ports under 1024 are privileged, meaning only services which at least start with root privileges can bind to them (often, service webservers subsequently hand off to something with less privileges). What you can do, however, is run your application on another, non-privileged port such as Port 8080, and then forward requests from Port 80 to that port. You can then set the environment variable PORT=8080 instead.

As a temporary iptables command, we could add a REDIRECT rule to the NAT table PREROUTING chain for TCP from Port 80 to Port 8080 using something like this:

iptables -t nat -A PREROUTING -p tcp -m tcp --dport 80 -j REDIRECT --to-ports 8080

This is only to demonstrate what we’re doing, however; such a rule issued at the command-line would not be permanent. I’m using the Puppet firewall module, however, which supports a firewall resource. Henceforth, I assume you’ve set up your firewall according to the module instructions; this is very important. (Please remember to allow established connections, SSH, and to leave a window with an active SSH connection open somewhere whilst you activate the firewall, checking you can establish a new connection before closing it.) Once you’re sure your firewall’s working properly, use Puppet to accomplish the same as the iptables command above:

firewall {'102 forward port 80 to 8080':
  table       => 'nat',
  chain       => 'PREROUTING',
  proto       => 'tcp',
  dport       => '80',
  jump        => 'REDIRECT',
  toports     => '8080'
}

After a Puppet run, list the NAT table rules and verify that the rule has been inserted correctly:

iptables -L -t nat

With any luck, you’ve successfully forwarded Port 80 to Port 8080 (or similar) without locking yourself out of SSH. ;)

Peace,
tiredpixel ☮

obsession

—what’s that perfume?
—it’s obsession!
—calvin klein?
—no—remember i borrowed your jumper? i boiled it and made my own perfume.
would you like some?

how to deploy clojure applications using bakesale

In this guide, I show how to deploy Clojure applications using bakesale. I wrote bakesale because I wanted to return to the main ingredients of deployment, doing only so much as is finger-saving, and leaving the rest to the user to sort. I find this gives a lot of flexibility, whilst not cluttering my deployment config with lots of custom settings. bakesale is written as a collection of simple Shell scripts, and is pretty transparent. It’s built around the premise that deployment is much like baking a cake for a bakesale. Although I usually use it to deploy Ruby applications, deploying Clojure applications is just as straightforward. For this guide, I deploy MTRX9 (GitHub; Twitter), a simple websockets matrix monitoring tool which uses the notions of streams, chars, and time.

server prerequisites

The server, running Ubuntu 12.04 LTS, has a sudoer user called mtrx9, with a corresponding home directory. RubyGems is installed as a package, because I’ll use Foreman to write upstart scripts on the server. This isn’t necessary, of course, but it’s pretty convenient. Note that this server isn’t running Ruby applications, so I’m not using RVM or another version manager; instead, I just let the package dependencies get satisfied. I have Java OpenJDK installed, again as a package.

I manage these prerequisites using Puppet (a masterless setup, also deployed using bakesale); however, this is about deploying Clojure, so I’ll assume you’ve set up the server in whichever way makes you happy. To manually install the necessary packages:

apt-get install rubygems openjdk-7-jdk

install bakesale

bakesale just needs to be somewhere on your local system so that you can source it in scripts. I assume a local user called mlnw, with a corresponding home directory at /Users/mlnw/. I also assume your code repository is at /Users/mlnw/mtrx9/. Clone bakesale somewhere:

git clone git://github.com/tiredpixel/bakesale.git /Users/mlnw/bakesale

That’s all that’s needed to use bakesale; by default, the master branch will be used, which is (hopefully) stable.

define settings and services

bakesale discourages having the deployment config in the code repository itself (let’s stop embedding config in repositories). Create a directory to hold your deployment config:

mkdir -p /Users/mlnw/Deployments/mtrx9/

Create a file containing environment variables for the application; this will be exported to upstart using Foreman. MTRX9 only uses environment variables settings, but if you have other settings, you can write these in a similar manner. Copy the example and edit as appropriate:

cp /Users/mlnw/mtrx9/.env.example /Users/mlnw/Deployments/mtrx9/mtrx9.env

The Procfile defines services for the application. We will compile into a standalone uberjar, so write a custom Procfile:

# /Users/mlnw/Deployments/mtrx9/mtrx9.Procfile

web: java -jar target/*-*-standalone.jar $PORT

write deployment recipe

The deployment recipe defines the details of the deployment. However, it’s just a Shell script, so you can put whatever custom logic in there you need. If you find yourself using the same small fragment of code a lot across scripts, then it might be a good candidate for a new bakesale ‘stage’. It would be excellent if you would be so kind as to fork bakesale and submit a pull request with your improvement. Include bakesale at the top of your script:

source /Users/mlnw/bakesale/bakesale.sh

Then, just write a Shell script to deploy the application, using the bakesale ‘stage’ helpers. Without further ado, here is the complete script for deploying MTRX9. It clones the MTRX9 repository (fresh each time, by design), writes the settings and services configs, compiles the application into an uberjar, rsyncs everything to the server, uses Foreman to export it to upstart (Foreman gets automatically installed, if it’s not already, as part of that command), and restarts the exported services. This only takes a few lines:

# /Users/mlnw/Deployments/mtrx9/mtrx9.sh

#!/bin/bash

source /Users/mlnw/bakesale/bakesale.sh

ssh1=mtrx9@example.com

sshs="($ssh1)"

# = Bake

bakesale bake git git://github.com/tiredpixel/mtrx9.git

bakesale bake copy /Users/mlnw/Deployments/mtrx9/mtrx9.env .env
bakesale bake copy /Users/mlnw/Deployments/mtrx9/mtrx9.Procfile Procfile

bash -c "cd '$bakesale_cakebox/'; lein uberjar" # or any other custom step

# = Carry

bakesale carry rsync_ssh "$sshs" mtrx9/

# = Wave

bakesale wave ssh_foreman "$sshs" "export --root mtrx9/ --app mtrx9 --user mtrx9 --concurrency web=1 upstart /etc/init; restart mtrx9"

bakesale supports multi-server deployments; just pass an array of locations to the SSH commands.

deploy application

The deployment config is just a script, so to deploy, just execute it:

bash /Users/mlnw/Deployments/mtrx9/mtrx9.sh

At some point, you’ll be asked for the sudo password; this is necessary so Foreman can export it to upstart.

comments

bakesale isn’t particularly fast at deployment; there are a few reasons for this. One is that a fresh clone of the code repository is made each time; this is to provide absolute assurance that nothing’s been included by accident. Another reason for slowness is that the ‘cakebox’ is rsynced up to the server, rather than letting the server perform the clone; this is so the server doesn’t need to have Git installed, and doesn’t require the server to have access to the code repository. bakesale is just as opinionated as any other deployer; it doesn’t claim to be the best solution, only a solution built on a certain set of ideas (such as not having to pull hair out about server access to repositories, and forwarded SSH settings). It’s still a young project, so if you’d like to help improve it, please do get in contact—or just fork it and see.

Peace,
tiredpixel ☮