Skip to content

Puppet Module: Nginx & PHP5-FastCGI (opt.), vhosts, proxies, redirections, IPv6, SSL, and so on.

License

Notifications You must be signed in to change notification settings

nerdlich/puppetlabs-nginxpack

 
 

Repository files navigation

#Nginxpack Build Status

####Table of Contents

  1. Overview
  2. Module Description
  3. What nginxpack affects
  4. Usage
  5. Common Use Cases
  6. Limitations (only Debian-likes)
  7. Development

##Overview

This module installs and configures Nginx (lightweight and robust webserver). It's a pack because you can optionally install and configure PHP5 at the same time. There are three types of vhost available (basic, proxy and redirection) and some smart options for Nginx and PHP. This module is full IPv6 compliant because we are in 2013.

##Module Description

Features available:

  • Install and configure Nginx
  • Optionally: install and configure PHP5-FastCGI
  • Optionally: install PHP-MySQL connector and/or others PHP5 modules
  • Basic vhosts
  • Proxy vhosts
  • 301 Redirection vhosts
  • SSL support
  • Full IPv6 compliant (and still IPv4...)
  • Automatic blackhole for non-existent domains
  • Several options (upload limits with Nginx/PHP, timezone, logrotate, default SSL certificate, htpasswd, XSS injection protection, etc.)
  • Custom configuration option for non-supported features

##What nginxpack affects

Installed packages:

  • nginx
  • With enable_php: php5-cgi, spawn-fcgi
  • With php_mysql: php5-mysql
  • With logrotate: logrotate, psmisc (if not already present)

logrotate is used with a configuration file in /etc/logrotate.d/nginx allowing it to daily rotate vhost logs. The configuration uses killall from psmisc in order to force nginx to update his inodes (this is the classic way). killall is also used in nginxpack::php::cgi to ensure that PHP is not still running.

Use nginxpack::php::mod { 'foo' } involves installing php5-foo.

Added services:

  • Use /etc/init.d/nginx
  • Add /etc/init.d/php-fastcgi (and associated script /usr/bin/php-fastcgi.sh)

Added files:

  • Vhosts: /etc/nginx/sites-{available,enabled}/* and /etc/nginx/include/*
  • Logs: /var/log/nginx/<vhostname>/{access,error}.log
  • Certificates: /etc/nginx/ssl/*
  • Script for automatic blackholes: /etc/nginx/find_default_listen.sh

Use php_timezone, php_upload_max_filesize and/or php_upload_max_files affects /etc/php5/cgi/php.ini (but not overrides it).

##Usage

###Beginning with nginxpack

####Webserver

If you just want a webserver installing with the default options and without PHP you can run:

include 'nginxpack'

And with PHP:

class { 'nginxpack':
  enable_php => true,
}

With PHP-MySQL connector:

class { 'nginxpack':
  enable_php => true,
  php_mysql  => true,
}

Others options for PHP:

class { 'nginxpack':
  enable_php              => true,
  php_timezone            => 'Antarctica/Vostok',
  php_upload_max_filesize => '1G',
  php_upload_max_files    => 5,
}

With this example, you will be able to propose uploads of 5 files of 1G max each together. In this case, the POST-data size limit (from PHP) will automatically be configured to accept until 5G.

You can also configure default https configuration here. See the first common use case.

####Basic Vhost

Standard vhost.

Listen on all IPv6/IPv4 available with port 80, no PHP and no SSL:

nginxpack::vhost::basic { 'foobar':
  domains => [ 'foobar.example.com' ],
}

Using aliases:

nginxpack::vhost::basic { 'foobar':
  domains => [ 'foobar.example.com', 'www.foobar.example.com' ]
}

With PHP:

nginxpack::vhost::basic { 'foobar':
  domains => [ 'foobar.example.com' ],
  use_php => true,
}

Since you use use_php for at least one vhost, you have to use enable_php with the webserver.

Listen on a specific IPv6 and all IPv4 available:

nginxpack::vhost::basic { 'foobar':
  domains => [ 'foobar.example.com' ],
  ipv6    => '2001:db8::42',
}

You can use the ipv4 option to listen on a specific IPv4 address or disable it with false (real _wo_men do that). ipv6 also can be set to false, but please don't do that.

Listen on a specific port:

nginxpack::vhost::basic { 'foobar':
  domains => [ 'foobar.example.com' ],
  port    => 8080,
}

With SSL (https://):

nginxpack::vhost::basic { 'foobar':
  domains         => [ 'foobar.example.com' ],
  https           => true,
  ssl_cert_source => 'puppet:///certificates/foobar.pem',
  ssl_key_source  => 'puppet:///certificates/foobar.key',
}

Generate pem (crt) and key files (put your full qualified domain name in Common Name):

$ openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout foobar.key -out foobar.pem

You also could use ssl_cert_content and ssl_key_content to define the certificate from a string (useful if you use hiera to store your certificates: ssl_cert_content => hiera('foobar-cert')).

The default listening port becomes 443 but you still could force a different one with port.

Other options:

nginxpack::vhost::basic { 'foobar':
  domains         => [ 'foobar.example.com' ],
  enable          => false,
  files_dir       => '/srv/websites/foobar/',
  injectionsafe   => true,
  upload_max_size => '5G',
  htpasswd        => 'user1:$apr1$EUoQVU1i$kcGRxeBAJaMuWYud6fxZL/',
  forbidden       => [ '^/logs/', '^/tmp/', '\.inc$' ],
}

files_dir's default value is /var/www/<name>/ (e.g. /var/www/foobar/).

injectionsafe applies these protections against XSS injections. These restrictions might be incompatible with your applications.

upload_max_size should be in line with php_upload_max_filesize x php_upload_max_files

htpasswd's value can be generated from a command line tool (apache2-utils):

$ htpasswd -nb user1 secretpassword

If you want to use a specific configuration for a specific vhost, you can use add_config_source or add_config_content to inject custom Nginx instructions directly in server { }.

####Proxy Vhost

Reverse-proxy vhost allowing you to seamlessly redirect the traffic to a remote webserver.

Default listen identical to basic vhosts and remote server reached on port 80 without using SSL:

nginxpack::vhost::proxy { 'foobarlan':
  domains   => [ 'foobar.example.com' ],
  to_domain => 'foobar.lan',
}

Remote SSL and different remote port:

nginxpack::vhost::proxy { 'foobarlan':
  domains   => [ 'foobar.example.com' ],
  to_domain => 'foobar.lan',
  to_https  => true,
  to_port   => 8080,
}

Default remote port is 80. In this case it would have been 443 due to to_https.

SSL (https://) is usable in the same manner as basic vhosts.

Options ipv6, ipv4, port, enable, add_config_source, add_config_content and upload_max_size are also available in the same way as basic vhosts.

####Redirection Vhost

General redirection (using 301 http code) allowing you to officially redirect any requests to a remote domain. In short: http://foobar.example.com/(.*) => http://foobar.com/$1.

Default listen identical to basic vhosts, and remote domain reached on port 80 without using SSL:

nginxpack::vhost::redirection { 'foobarlan':
  domains   => [ 'foobar.example.com' ],
  to_domain => 'www.foobar.com',
}

Options to_https and to_port are available in the same way as proxy vhosts.

Options ipv6, ipv4, port, enable, add_config_source and add_config_content are available in the same way as basic vhosts.

###Documentation

The previous section should be clear enough to understand the possibilities of nginxpack.

If you want a detailed documentation of types and options, there is a full documentation in the headers of the Puppet files.

###Default Vhosts

####Automatic Blackholes

Have a determinist way to access to the vhosts is a good practice in web security. If you say that a vhost can be reached via my.example.com, any request using another domain should not success. If you do not have a default vhost with a listen line for each port used on the webserver, Nginx will use a doubful algorithm to determine which vhost is usable in the case of an unknown domain.

Good news! Nginxpack creates this default vhost for you and redirects any request out of your scopes to a blackhole.

If you use at least one vhost with SSL, you need to define ssl_default_* options. See the next section about SSL.

####Well-known problem with SSL

The full circle is easy to understand:

  1. Nginx chooses the correct vhost (among those who are listening on the correct port and IP) thanks to the host field (HTTP 1.1).
  2. When a client initiates a SSL connection, this field is encrypted, until Nginx decrypts the request.
  3. Informations about decryption (e.g. certificate location) are in the correct vhost. Back to 1.

Thus, if you have several vhosts listening on the same port with the same IP (or all IP) and that use SSL, you have a problem.

The good solution is to use a default vhost, listening on all ports and IP used by SSL vhosts on the webserver and containing the decryption informations. When Nginx will receive a SSL request, it will use this vhost, and so, will be able to decrypt it. Once the host field is readable, it can chose the correct vhost. The latter don't have to propose SSL but it absolutely must listen on port 443 (or another if you use SSL with another one).

Good news! Nginxpack creates this default vhost for you if you use ssl_default_cert_source (or ssl_default_cert_key) and ssl_default_key_source (or ssl_default_key_source) options. This certificate must be valid for all domains used, so it will probably be a wildcard certificate.

The first common use case in the next section provides an example.

##Common Use Cases

###Reverse-proxy with IPv4

####Multiple servers and a single IPv4

You are in charge of servers in the wrong decade: there is almost no more IPv4 but you still can't use only IPv6. Thus, your provider has provided you as many IPv6 addresses as there are grains of sand on earth, but only one poor IPv4.

If you have various webservers (on remote machines or in containers beside) behind this access, you need to have a reverse-proxy. In the following examples, we consider that your firewall redirects ports 80 and 443 to the server corresponding to your reverse-proxy for any incoming IPv4 flux (probably by configuring your NATPT on your CPE if you are at home) .

The first example considers that your ISP provides you IPv6 addresses and that you are able to use it. Second example considers that you have a crappy ISP and so no IPv6 addresses available. In both examples, we want a blog (http), a wiki (https) and a members panel (https). For the sake of cleanliness and security reasons, each website must have its own webserver on its own machine/container.

####With usable IPv6 addresses

Goals:

  • 1 webserver (1 machine/container) by website and 1 for the reverse-proxy.
  • IPv6 clients reach websites directly and can't use the reverse-proxy.
  • IPv4 clients reach websites only through the reverse-proxy and can't reach them directly.
  • Therefore, communications between the reverse-proxy and the remote websites use IPv6, but it's seamless for (IPv4) clients and there is no problem with IPv6 over the internal network.

The certificat location is provided on the reverse-proxy for IPv4 clients (see previous section), and on the vhosts for IPv6 clients. Proxy vhosts listen on port 443 but not have SSL capabilities (see again the previous section).

Webserver hosting the reverse-proxy:

# Could be replaced by an internal DNS server
host {
  'blog.lan':
    ip => '2001:db8::a';
  'wiki.lan':
    ip => '2001:db8::b';
  'members.lan':
    ip => '2001:db8::c';
}

# Wildcard certificate (*.example.com)
class { 'nginxpack':
  ssl_default_cert_source => 'puppet:///certificates/default.pem',
  ssl_default_key_source  => 'puppet:///certificates/default.key',
}

nginxpack::vhost::proxy { 'blog':
  domains   => [ 'blog.example.com' ],
  ipv6      => false,
  to_domain => 'blog.lan',
}

nginxpack::vhost::proxy { 'wiki':
  domains   => [ 'wiki.example.com' ],
  port      => 443,
  ipv6      => false,
  to_domain => 'wiki.lan',
  to_https  => true,
}

nginxpack::vhost::proxy { 'members':
  domains   => [ 'members.example.com' ],
  port      => 443,
  ipv6      => false,
  to_domain => 'members.lan',
  to_https  => true,
}

Webserver hosting blog.example.com:

nginxpack::vhost::basic { 'blog':
  domains => [ 'blog.example.com' ],
  ipv4    => false,
  use_php => true,
}

Webserver hosting wiki.example.com:

nginxpack::vhost::basic { 'wiki':
  domains         => [ 'wiki.example.com' ],
  https           => true,
  ssl_cert_source => 'puppet:///certificates/default.pem',
  ssl_key_source  => 'puppet:///certificates/default.key',
  ipv4            => false,
  use_php         => true,
}

Webserver hosting members.example.com:

nginxpack::vhost::basic { 'members':
  domains         => [ 'members.example.com' ],
  https           => true,
  ssl_cert_source => 'puppet:///certificates/default.pem',
  ssl_key_source  => 'puppet:///certificates/default.key',
  ipv4            => false,
  use_php         => true,
}

####Without IPv6 addresses

Goals:

  • 1 webserver (1 machine/container) by website and 1 for the reverse-proxy.
  • Clients reach websites only through the reverse-proxy and can't reach them directly.
  • We trust in the internal network so communications between the reverse-proxy and the websites are never encrypted.

Webserver hosting the reverse-proxy:

# Could be replaced by an internal DNS server
host {
  'blog.lan':
    ip => '10.0.0.10';
  'wiki.lan':
    ip => '10.0.0.20';
  'members.lan':
    ip => '10.0.0.30';
}

# Wildcard certificate (*.example.com)
class { 'nginxpack':
  ssl_default_cert_source => 'puppet:///certificates/default.pem',
  ssl_default_key_source  => 'puppet:///certificates/default.key',
}

nginxpack::vhost::proxy { 'blog':
  domains   => [ 'blog.example.com' ],
  to_domain => 'blog.lan',
}

nginxpack::vhost::proxy { 'wiki':
  domains   => [ 'wiki.example.com' ],
  port      => 443,
  to_domain => 'wiki.lan',
}

nginxpack::vhost::proxy { 'members':
  domains   => [ 'members.example.com' ],
  port      => 443,
  to_domain => 'members.lan',
}

Webserver hosting blog.example.com:

nginxpack::vhost::basic { 'blog':
  domains => [ 'blog.example.com' ],
  use_php => true,
}

Webserver hosting wiki.example.com:

nginxpack::vhost::basic { 'wiki':
  domains => [ 'wiki.example.com' ],
  use_php => true,
}

Webserver hosting members.example.com:

nginxpack::vhost::basic { 'members':
  domains => [ 'members.example.com' ],
  use_php => true,
}

###Usage of www.

Using www.example.com is so 2005 and you want automatically redirect all requests from http://www.example.com/.* to http://example.com/$1.

nginxpack::vhost::basic { 'eatmytux':
  domains => [ 'example.com' ],
  use_php => true,
}

nginxpack::vhost::redirection { 'www-eatmytux':
  domains   => [ 'www.example.com' ],
  to_domain => 'example.com',
}

###Port Redirection

Proxy and redirection vhosts use the first value of domains when to_domain is absent.

####Seamlessly

Your out-of-the-box webapp listens on port 8080 but you want use it on port 80 without modifying its configuration:

nginxpack::vhost::proxy { 'mywebapp':
  domains => [ 'example.com' ],
  to_port => 8080,
}

####Not Seamlessly

Visible location switching (the client will see his URL transformation: http://example.com/.* => http://example.com:8080/$1) means redirection:

nginxpack::vhost::redirection { 'mywebapp':
  domains => [ 'example.com' ],
  to_port => 8080,
}

####HTTPS Redirection

Spontaneous switching from http to https:

nginxpack::vhost::basic { 'wiki':
  domains         => [ 'wiki.example.com' ],
  https           => true,
  ssl_cert_source => 'puppet:///certificates/wiki.pem',
  ssl_key_source  => 'puppet:///certificates/wiki.key',
}

nginxpack::vhost::redirection { 'https-wiki':
  domains  => [ 'wiki.example.com' ],
  to_https => true,
}

####IPv6/IPv4 Proxy

You own a website not available in IPv6 and you cannot have an IPv6 address on its machine. A way to solving this problem is to create a proxy on a dual-stack machine (listening on IPv6 to accept incoming requests and listening on IPv4 to contact the remote webserver):

nginxpack::vhost::proxy { 'foobar':
  domains   => [ 'example.com' ],
  to_domain => 'ip4.example.com',
  ipv4      => false,
}

DNS configuration:

example.com
    AAAA proxy
    A    webserver
ip4.example.com
    A    webserver

This trick could also be used in the opposite case.

##Dependencies

  • puppetlabs/stdlib >= 3.x (file_line is used to edit php.ini and ensure_packages to install logrotate and psmisc)

##Limitations

This module is only available for Debian-likes.

Tests are made with:

  • Debian Wheezy 7.1
  • Puppet 3.2.4 (Build Tests)
  • Nginx 1.2.1
  • PHP 5.4.4

This does not mean that this module can't be used with other versions but I have no idea about the compatibility.

##Development

I developed this module for my own needs but I think it's generic enough to be useful for someone else.

Feel free to contribute. I'm not a big fan of centralized services like GitHub but I used it to permit easy pull-requests, so show me that's a good idea!

Thank Lorraine Data Network for testing the module.

About

Puppet Module: Nginx & PHP5-FastCGI (opt.), vhosts, proxies, redirections, IPv6, SSL, and so on.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Puppet 59.4%
  • Ruby 37.8%
  • Shell 2.8%