Secure, Usable chrooted users

Recently I've been helping with locking down Debian server for a friend to allow many untrusted users and websites to be let loose on the system without worrying too much about a system compromise and without the overhead of virtual servers.

Currently our software stack consists of:

  • grsecurity for system & chroot hardening
  • Global Apache instance
  • Multiple Debian instances in /var/chroot created with debootstrap
  • pam_chroot to jail individual users
  • SuPHP (with chroot support)

Cron Support

Because we'd like users to have everything they'd normally use that means cron is a must-have feature, Vixie cron uses PAM on Debian so it can take advantage of individual user chroot preferences by default.

The only problem was: How do chrooted users edit their crontab? Simple, just bind mount the global cron spool inside each of the chroots, and we can avoid information leakage with correct directory permissions.

Currently each of the chroot environments has mounts setup which look like this:

proc-chroot     /var/chroot/www/proc            proc    defaults        0       0
devpts-chroot   /var/chroot/www/dev/pts         devpts  defaults        0       0
/var/spool/cron /var/chroot/www/var/spool/cron  none    bind            0       0

SuPHP

My only major gripe with SuPHP (and others like cgiwrap) is that they don't support PAM (or pam_chroot) which makes my life as a sysadmin much more difficult.

Our hacky workaround for this is to have one specific chroot for websites for PHP and CGI scripts to execute in, but I'll probably end up adding PAM support to cgiwrap and SuPHP out of sheer frustration with their inflexibility as it stands.

Automation

Unfortunately all this requires a few extra steps when creating the user to keep the system secure, and then a few more things to jail the new user in a chroot.

I'll be providing my scripts later on which move system users in and out of chroot environments while keeping the system regular with a helping of symlinks and tweaks.

Limited Directory Accounts

Sometimes we're required to setup new users with access to only a single directory (e.g. the templates directory of a web application) and this presents a problem: setup a full chroot for that user with only the required directory access? That's often way too much effort and overhead.

As a result we're still running a legacy FTP daemon which allows virtual chroot to a specific directory on a per-user or per-group basis.

Things To Do?

Other than PAM support for SuPHP and cgiwrap, the disk space overhead for each chroot is around 180mb which at any rate is quite costly. Ideally I would like to bring this down to under 5 MiB with the help of UnionFS or AuFS, this would also reduce the management overhead allowing us to maintain a single secure base chroot with possibly hundreds of others based on that.

Sending local e-mail from the main server to chrooted users works (when using the ~/Maildir format), however it's currently not possible to send e-mail from within a chroot until we have something like ssmtp setup to do local proxying to the main Exim instance.

Skip to Page:  1 2 3 … 8

Kohana Virtual Hosts

If you intend to run multiple Kohana applications from the same code base, the separation of the system & modules directories from the application and it's themes is the standard way to go.

For most shared applications you'd develop the functionality and it's views as separate individual modules, while the only thing the application directory needs to hold are configuration files for separate databases and the theme-specific views of which there may only be one or two along with the cache & log directories.

The other overhead is maintaining Apache virtual hosts, setting up mod_vhost_alias which may not be possible if you don't rent a VPS or dedicated server. By making a few small modifications to your Kohana index.php file you can keep all your applications in a single directory and cut down the amount of admin stuff you have to do.

$kohana_application = $_SERVER['SERVER_NAME'];
if( ! file_exists($kohana_application) )
{
	$kohana_application = 'default';
}

If you have a dedicated IP address you can just point new domains to it and presto! it'll work as long as you have an application directory with the same name as the domain name. An example directory structure could be like:

kohana/                   # Kohana SVN trunk
modules/                  # Application modules
root/                     # Web root
|-index.php               # Kohana dispatcher
|-@harry2.example.com/    # Symlink to harry.exmaple.com
|
|-harry.example.com/
| |-cache/
| |-log/
| `-views/
|   |-header.php
|    `-footer.php
`-admin.example.com/
  |-cache/
  `-log/
    .. etc.    
Skip to Page:  1 2 3 … 8

Powered by SimpleCDN

With a little bit of persuasion by the YSlow plugin for FireBug I took the plunge and decided to try out SimpleCDN.com with the 15 free credits.

Although I use nearly very few images on my website, having an alternate server local to the user should speed things up even more and integrating AutoCDN with your site is just a matter of prefixing your links.

Considering most of my images are under 1kb each, and the stylesheet is a meager 11k, it would allow me 150 similar items to be hosted which fall in the under 100kb category in their pricing structure. With free updates to existing files of the same size it's a win-win situation; I presume the updates are free as long as they don't go into the next pricing category which leaves you some leeway for small differences in size.

World Coverage

With SimpleCDN being a relatively new startup and no concrete information on their website about their I can only presume they are US centric at the moment, based on traceroutes from the UK, USA, Australia and India they are peering with resisoft.com which has some pretty good links with HE, Teleglobe, GlobalCrossing and all the other usual suspects.

This is a disadvantage compared to other CDN services which provide truly global distribution with local bandwidth in Europe or even bandwidth starved countries that matter like Australia.

Account Management

Simple CDN Account Management screenshot

All I can say is the management interface is painful to use, iframes within iframes and multiple scrolling areas on the page at any time (often within each other) makes it very distracting to find the information you need; on top of this it's very very AJAX heavy.

Not to mention the whole management interface is designed for a widescreen display at presumably 1440 pixels or wider, so those of us using only 1280x1024 have to scroll around and are greeted with an strange page that's only half there or where the menu consumes more than a third of the screen space available.

The final result (even at full screen) is a user interface which I need to scroll around to get basic things done, with page tearing at slow frame rates.

Conclusion

While it's extremely cheap for small images such as avatars, CSS stylesheets, Javascript or other small files with very reasonable pricing for larger files if you just need to offload bandwidth and static files, the lack of global coverage and poor web admin interface equate it to being a feature-rich scalable file hosting service trying to play with the big boys.

Don't get me wrong, there are lots of cool features, but my original aims were to find a global content distribution service, so with the low price you'll have to compromise.

Skip to Page:  1 2 3 … 8

Flexible PHP Interfaces

While working on converting old and flaky code to our current framework, making it more object orientated, with less duplication and easier to understand and use I thought I'd cover a few things which to me make classes, methods and interfaces "Programmer Friendly".

1. Doc Comments

You might be in a hurry, but documentation comments can speed up development to an order of magnitude. With most advanced PHP editors such as PDT supporting JavaDoc style comments and subsequently type hinting from them for auto-completion, taking a few extra seconds to add these annotations is well worth it.

interface BelongsToCompany {
  /**
   * @return Company
   */
  public function company();
}

2. Flexible Parameters

Always require the minimum amount of data from the programmer to run the function. For example if you only require the `Company` primary key to run some queries, the only require the primary key as a parameter.

However it can be useful to convert known input types into what you want, allowing the programmer to pass a Company object or a User object (which belongs to a Company) as the parameters and to get the company ID from that.

class Something {
  /**
   * @param integer|Company $company_id
   */
  public function staffCount( $company_id )
  {
    if( $company_id instanceof BelongsToCompany ) {
      $company_id = $company_id->company();
    }

    if( $company instanceof Company ) {
      $company_id = $company_id->id;
    }
  }
}

3. Use method Entry contracts

Wherever you implement an interface method, and generally any method, add input contracts using asserts() for more robust code and faster less obscure error feedback to other programmers. A fine balance is needed between allowing flexible parameters usually setup above the entry contract, and ensuring that the main body of code only runs with the correct types of parameters.

I'm including this under the topic of flexibility because these provide safeguards to let the programmer know when then underlying function cannot handle or automatically convert the parameters.

Some situations would be:

  • $parameter is a number
  • $parameter is an instance of SomeClass or SomeInterface
  • If $param1 is passed, ensure $param2 is there too
  • Ensure $parameter is not empty

However, avoid using `is_int` or `is_bool`, or other underlying functions which query the underlying ZVAL structure in entry contracts; instead use the `ctype_*` functions or type-casts first, then check that the values are within expected ranges.

/**
 * @param integer $p1
 * @param boolean $p2
 * @param string $p3
 */
function testContract( $p1, $p2, $p3 ) {
  $p1 = (int)$p1;
  assert( $p1 > 0 );

  // Boolean is implicitly within range
  $p2 = (boolean)$p2;

  assert( !empty($p3) );
  assert( strlen($p3) < 100 );
}

4. Be Stateful and Refactor

One of my major gripes are groups of 'stateless' static methods which all accept a common parameter. These should be flagged early on for re-factoring as they make your classes brittle and inhibit changes in future. Common places where these pop up are when procedural code has been hastily converted into classes.

class Company {
  public static function getUsers( $company_id ) {}
  public static function getPeople( $company_id ) {}
  public static function getCustomers( $company_id ) {}
}

One of the problem factors here is that the programmer is required to carry the objects state around wherever it's being used and implicitly pass it when calling the methods. A particularly bad example would be:

$users = Company::getUsers( $this->company->id );
Skip to Page:  1 2 3 … 8

About

Harry is a professional developer and sysadmin from London, UK.

He's an atheist, employed at PixelMags LLC, a socialist and has a pragmatic outlook on life, love and religion.

Bookmarks

I'm constantly finding interesting stuff, here are some of the things I've bookmarked recently:

HarryR on Faves.com