1
0
mirror of https://github.com/certbot/certbot.git synced 2026-01-21 19:01:07 +03:00

Merge branch 'master' into boulder

This commit is contained in:
James Kasten
2015-04-22 23:19:12 -07:00
49 changed files with 3381 additions and 25 deletions

1
.gitignore vendored
View File

@@ -1,5 +1,6 @@
*.pyc
*.egg-info
.eggs/
build/
dist/
venv/

View File

@@ -1,7 +1,7 @@
language: python
# http://docs.travis-ci.com/user/ci-environment/#CI-environment-OS
before_install: travis_retry ./bootstrap/ubuntu.sh
before_install: travis_retry sudo ./bootstrap/ubuntu.sh
install: "travis_retry pip install tox coveralls"
script: "travis_retry tox"

View File

@@ -1,4 +1,14 @@
Let's Encrypt Preview:
Copyright (c) Internet Security Research Group
Licensed Apache Version 2.0
Incorporating code from nginxparser
Copyright (c) 2014 Fatih Erikli
Licensed MIT
Text of Apache License
======================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
@@ -173,3 +183,23 @@
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
Text of MIT License
===================
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

2
Vagrantfile vendored
View File

@@ -7,7 +7,7 @@ VAGRANTFILE_API_VERSION = "2"
# Setup instructions from docs/using.rst
$ubuntu_setup_script = <<SETUP_SCRIPT
cd /vagrant
./bootstrap/ubuntu.sh
sudo ./bootstrap/ubuntu.sh
if [ ! -d "venv" ]; then
virtualenv --no-site-packages -p python2 venv
./venv/bin/python setup.py dev

35
bootstrap/_deb_common.sh Executable file
View File

@@ -0,0 +1,35 @@
#!/bin/sh
# Tested with:
# - Ubuntu:
# - 12.04 (x64, Travis)
# - 14.04 (x64, Vagrant)
# - 14.10 (x64)
# - Debian:
# - 6.0.10 "squeeze" (x64)
# - 7.8 "wheezy" (x64)
# - 8.0 "jessie" (x64)
# virtualenv binary can be found in different packages depending on
# distro version (#346)
distro=$(lsb_release -si)
# 6.0.10 => 60, 14.04 => 1404
version=$(lsb_release -sr | awk -F '.' '{print $1 $2}')
if [ "$distro" = "Ubuntu" -a "$version" -ge 1410 ]
then
virtualenv="virtualenv"
elif [ "$distro" = "Debian" -a "$version" -ge 80 ]
then
virtualenv="virtualenv"
else
virtualenv="python-virtualenv"
fi
# dpkg-dev: dpkg-architecture binary necessary to compile M2Crypto, c.f.
# #276, https://github.com/martinpaljak/M2Crypto/issues/62,
# M2Crypto setup.py:add_multiarch_paths
apt-get update
apt-get install -y --no-install-recommends \
python python-setuptools "$virtualenv" python-dev gcc swig \
dialog libaugeas0 libssl-dev libffi-dev ca-certificates dpkg-dev

View File

@@ -1 +1 @@
ubuntu.sh
_deb_common.sh

View File

@@ -1,2 +1,2 @@
#!/bin/sh
sudo brew install augeas swig
brew install augeas swig

View File

@@ -1,15 +0,0 @@
#!/bin/sh
# Tested with:
# - 12.04 (x64, Travis)
# - 14.04 (x64, Vagrant)
# - 14.10 (x64)
# dpkg-dev: dpkg-architecture binary necessary to compile M2Crypto, c.f.
# #276, https://github.com/martinpaljak/M2Crypto/issues/62,
# M2Crypto setup.py:add_multiarch_paths
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
python python-setuptools python-virtualenv python-dev gcc swig \
dialog libaugeas0 libssl-dev libffi-dev ca-certificates dpkg-dev

1
bootstrap/ubuntu.sh Symbolic link
View File

@@ -0,0 +1 @@
_deb_common.sh

View File

@@ -0,0 +1,35 @@
:mod:`letsencrypt.client.plugins.nginx`
----------------------------------------
.. automodule:: letsencrypt.client.plugins.nginx
:members:
:mod:`letsencrypt.client.plugins.nginx.configurator`
=====================================================
.. automodule:: letsencrypt.client.plugins.nginx.configurator
:members:
:mod:`letsencrypt.client.plugins.nginx.dvsni`
==============================================
.. automodule:: letsencrypt.client.plugins.nginx.dvsni
:members:
:mod:`letsencrypt.client.plugins.nginx.obj`
============================================
.. automodule:: letsencrypt.client.plugins.nginx.obj
:members:
:mod:`letsencrypt.client.plugins.nginx.parser`
===============================================
.. automodule:: letsencrypt.client.plugins.nginx.parser
:members:
:mod:`letsencrypt.client.plugins.nginx.nginxparser`
====================================================
.. automodule:: letsencrypt.client.plugins.nginx.nginxparser
:members:

View File

@@ -11,6 +11,7 @@ are provided mainly for the :ref:`developers <hacking>` reference.
In general:
* ``sudo`` is required as a suggested way of running privileged process
* `swig`_ is required for compiling `m2crypto`_
* `augeas`_ is required for the ``python-augeas`` bindings
@@ -20,7 +21,7 @@ Ubuntu
.. code-block:: shell
./bootstrap/ubuntu.sh
sudo ./bootstrap/ubuntu.sh
Debian
@@ -28,13 +29,10 @@ Debian
.. code-block:: shell
./bootstrap/debian.sh
sudo ./bootstrap/debian.sh
For squezze you will need to:
- Run ``apt-get install -y --no-install-recommends sudo`` as root
(``sudo`` is not installed by default) before running the bootstrap
script.
- Use ``virtualenv --no-site-packages -p python`` instead of ``-p python2``.
@@ -46,7 +44,7 @@ Mac OSX
.. code-block:: shell
./bootstrap/mac.sh
sudo ./bootstrap/mac.sh
Installation

View File

@@ -40,6 +40,12 @@ APACHE_REWRITE_HTTPS_ARGS = [
"""Apache rewrite rule arguments used for redirections to https vhost"""
NGINX_MOD_SSL_CONF = pkg_resources.resource_filename(
"letsencrypt.client.plugins.nginx", "options-ssl.conf")
"""Path to the Nginx mod_ssl config file found in the Let's Encrypt
distribution."""
DVSNI_CHALLENGE_PORT = 443
"""Port to perform DVSNI challenge."""

View File

@@ -129,6 +129,14 @@ class IConfig(zope.interface.Interface):
apache_mod_ssl_conf = zope.interface.Attribute(
"Contains standard Apache SSL directives.")
nginx_server_root = zope.interface.Attribute(
"Nginx server root directory.")
nginx_ctl = zope.interface.Attribute(
"Path to the 'nginx' binary, used for 'configtest' and "
"retrieving nginx version number.")
nginx_mod_ssl_conf = zope.interface.Attribute(
"Contains standard nginx SSL directives.")
class IInstaller(zope.interface.Interface):
"""Generic Let's Encrypt Installer Interface.

View File

@@ -0,0 +1 @@
"""Let's Encrypt client.plugins.nginx."""

View File

@@ -0,0 +1,561 @@
"""Nginx Configuration"""
import logging
import os
import re
import shutil
import socket
import subprocess
import sys
import zope.interface
from letsencrypt.acme import challenges
from letsencrypt.client import achallenges
from letsencrypt.client import constants
from letsencrypt.client import errors
from letsencrypt.client import interfaces
from letsencrypt.client import le_util
from letsencrypt.client import reverter
from letsencrypt.client.plugins.nginx import dvsni
from letsencrypt.client.plugins.nginx import parser
class NginxConfigurator(object):
# pylint: disable=too-many-instance-attributes,too-many-public-methods
"""Nginx configurator.
.. warning:: This plugin is a stub, does not support DVSNI yet!
.. todo:: Add proper support for comments in the config. Currently,
config files modified by the configurator will lose all their comments.
:ivar config: Configuration.
:type config: :class:`~letsencrypt.client.interfaces.IConfig`
:ivar parser: Handles low level parsing
:type parser: :class:`~letsencrypt.client.plugins.nginx.parser`
:ivar str save_notes: Human-readable config change notes
:ivar reverter: saves and reverts checkpoints
:type reverter: :class:`letsencrypt.client.reverter.Reverter`
:ivar tup version: version of Nginx
"""
zope.interface.implements(interfaces.IAuthenticator, interfaces.IInstaller)
description = "Nginx Web Server"
def __init__(self, config, version=None):
"""Initialize an Nginx Configurator.
:param tup version: version of Nginx as a tuple (1, 4, 7)
(used mostly for unittesting)
"""
self.config = config
# Verify that all directories and files exist with proper permissions
if os.geteuid() == 0:
self._verify_setup()
# Files to save
self.save_notes = ""
# Add number of outstanding challenges
self._chall_out = 0
# These will be set in the prepare function
self.parser = None
self.version = version
self._enhance_func = {} # TODO: Support at least redirects
# Set up reverter
self.reverter = reverter.Reverter(config)
self.reverter.recovery_routine()
# This is called in determine_authenticator and determine_installer
def prepare(self):
"""Prepare the authenticator/installer."""
self.parser = parser.NginxParser(
self.config.nginx_server_root,
self.config.nginx_mod_ssl_conf)
# Set Version
if self.version is None:
self.version = self.get_version()
temp_install(self.config.nginx_mod_ssl_conf)
# Entry point in main.py for installing cert
def deploy_cert(self, domain, cert, key, cert_chain=None):
# pylint: disable=unused-argument
"""Deploys certificate to specified virtual host.
.. note:: Aborts if the vhost is missing ssl_certificate or
ssl_certificate_key.
.. note:: Nginx doesn't have a cert chain directive, so the last
parameter is always ignored. It expects the cert file to have
the concatenated chain.
.. note:: This doesn't save the config files!
:param str domain: domain to deploy certificate
:param str cert: certificate filename
:param str key: private key filename
:param str cert_chain: certificate chain filename
"""
vhost = self.choose_vhost(domain)
directives = [['ssl_certificate', cert], ['ssl_certificate_key', key]]
try:
self.parser.add_server_directives(vhost.filep, vhost.names,
directives, True)
logging.info("Deployed Certificate to VirtualHost %s for %s",
vhost.filep, vhost.names)
except errors.LetsEncryptMisconfigurationError:
logging.warn(
"Cannot find a cert or key directive in %s for %s",
vhost.filep, vhost.names)
logging.warn("VirtualHost was not modified")
# Presumably break here so that the virtualhost is not modified
return False
self.save_notes += ("Changed vhost at %s with addresses of %s\n" %
(vhost.filep,
", ".join(str(addr) for addr in vhost.addrs)))
self.save_notes += "\tssl_certificate %s\n" % cert
self.save_notes += "\tssl_certificate_key %s\n" % key
#######################
# Vhost parsing methods
#######################
def choose_vhost(self, target_name):
"""Chooses a virtual host based on the given domain name.
.. note:: This makes the vhost SSL-enabled if it isn't already. Follows
Nginx's server block selection rules preferring blocks that are
already SSL.
.. todo:: This should maybe return list if no obvious answer
is presented.
.. todo:: The special name "$hostname" corresponds to the machine's
hostname. Currently we just ignore this.
:param str target_name: domain name
:returns: ssl vhost associated with name
:rtype: :class:`~letsencrypt.client.plugins.nginx.obj.VirtualHost`
"""
vhost = None
matches = self._get_ranked_matches(target_name)
if not matches:
# No matches at all :'(
pass
elif matches[0]['rank'] in xrange(2, 6):
# Wildcard match - need to find the longest one
rank = matches[0]['rank']
wildcards = [x for x in matches if x['rank'] == rank]
vhost = max(wildcards, key=lambda x: len(x['name']))['vhost']
else:
vhost = matches[0]['vhost']
if vhost is not None:
if not vhost.ssl:
self._make_server_ssl(vhost.filep, vhost.names)
return vhost
def _get_ranked_matches(self, target_name):
"""Returns a ranked list of vhosts that match target_name.
:param str target_name: The name to match
:returns: list of dicts containing the vhost, the matching name, and
the numerical rank
:rtype: list
"""
# Nginx chooses a matching server name for a request with precedence:
# 1. exact name match
# 2. longest wildcard name starting with *
# 3. longest wildcard name ending with *
# 4. first matching regex in order of appearance in the file
matches = []
for vhost in self.parser.get_vhosts():
name_type, name = parser.get_best_match(target_name, vhost.names)
if name_type == 'exact':
matches.append({'vhost': vhost,
'name': name,
'rank': 0 if vhost.ssl else 1})
elif name_type == 'wildcard_start':
matches.append({'vhost': vhost,
'name': name,
'rank': 2 if vhost.ssl else 3})
elif name_type == 'wildcard_end':
matches.append({'vhost': vhost,
'name': name,
'rank': 4 if vhost.ssl else 5})
elif name_type == 'regex':
matches.append({'vhost': vhost,
'name': name,
'rank': 6 if vhost.ssl else 7})
return sorted(matches, key=lambda x: x['rank'])
def get_all_names(self):
"""Returns all names found in the Nginx Configuration.
:returns: All ServerNames, ServerAliases, and reverse DNS entries for
virtual host addresses
:rtype: set
"""
all_names = set()
# Kept in same function to avoid multiple compilations of the regex
priv_ip_regex = (r"(^127\.0\.0\.1)|(^10\.)|(^172\.1[6-9]\.)|"
r"(^172\.2[0-9]\.)|(^172\.3[0-1]\.)|(^192\.168\.)")
private_ips = re.compile(priv_ip_regex)
hostname_regex = r"^(([a-z0-9]|[a-z0-9][a-z0-9\-]*[a-z0-9])\.)*[a-z]+$"
hostnames = re.compile(hostname_regex, re.IGNORECASE)
for vhost in self.parser.get_vhosts():
all_names.update(vhost.names)
for addr in vhost.addrs:
host = addr.get_addr()
if hostnames.match(host):
# If it's a hostname, add it to the names.
all_names.add(host)
elif not private_ips.match(host):
# If it isn't a private IP, do a reverse DNS lookup
# TODO: IPv6 support
try:
socket.inet_aton(host)
all_names.add(socket.gethostbyaddr(host)[0])
except (socket.error, socket.herror, socket.timeout):
continue
return all_names
def _make_server_ssl(self, filename, names):
"""Makes a server SSL based on server_name and filename by adding
a 'listen 443 ssl' directive to the server block.
.. todo:: Maybe this should create a new block instead of modifying
the existing one?
:param str filename: The absolute filename of the config file.
:param set names: The server names of the block to add SSL in
"""
self.parser.add_server_directives(
filename, names,
[['listen', '443 ssl'],
['ssl_certificate', '/etc/ssl/certs/ssl-cert-snakeoil.pem'],
['ssl_certificate_key', '/etc/ssl/private/ssl-cert-snakeoil.key'],
['include', self.parser.loc["ssl_options"]]])
def get_all_certs_keys(self):
"""Find all existing keys, certs from configuration.
:returns: list of tuples with form [(cert, key, path)]
cert - str path to certificate file
key - str path to associated key file
path - File path to configuration file.
:rtype: set
"""
return self.parser.get_all_certs_keys()
##################################
# enhancement methods (IInstaller)
##################################
def supported_enhancements(self): # pylint: disable=no-self-use
"""Returns currently supported enhancements."""
return []
def enhance(self, domain, enhancement, options=None):
"""Enhance configuration.
:param str domain: domain to enhance
:param str enhancement: enhancement type defined in
:const:`~letsencrypt.client.constants.ENHANCEMENTS`
:param options: options for the enhancement
See :const:`~letsencrypt.client.constants.ENHANCEMENTS`
documentation for appropriate parameter.
"""
try:
return self._enhance_func[enhancement](
self.choose_vhost(domain), options)
except (KeyError, ValueError):
raise errors.LetsEncryptConfiguratorError(
"Unsupported enhancement: {0}".format(enhancement))
except errors.LetsEncryptConfiguratorError:
logging.warn("Failed %s for %s", enhancement, domain)
######################################
# Nginx server management (IInstaller)
######################################
def restart(self):
"""Restarts nginx server.
:returns: Success
:rtype: bool
"""
return nginx_restart(self.config.nginx_ctl)
def config_test(self): # pylint: disable=no-self-use
"""Check the configuration of Nginx for errors.
:returns: Success
:rtype: bool
"""
try:
proc = subprocess.Popen(
[self.config.nginx_ctl, "-t"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
stdout, stderr = proc.communicate()
except (OSError, ValueError):
logging.fatal("Unable to run nginx config test")
sys.exit(1)
if proc.returncode != 0:
# Enter recovery routine...
logging.error("Config test failed")
logging.error(stdout)
logging.error(stderr)
return False
return True
def _verify_setup(self):
"""Verify the setup to ensure safe operating environment.
Make sure that files/directories are setup with appropriate permissions
Aim for defensive coding... make sure all input files
have permissions of root.
"""
uid = os.geteuid()
le_util.make_or_verify_dir(self.config.work_dir, 0o755, uid)
le_util.make_or_verify_dir(self.config.backup_dir, 0o755, uid)
le_util.make_or_verify_dir(self.config.config_dir, 0o755, uid)
def get_version(self):
"""Return version of Nginx Server.
Version is returned as tuple. (ie. 2.4.7 = (2, 4, 7))
:returns: version
:rtype: tuple
:raises errors.LetsEncryptConfiguratorError:
Unable to find Nginx version or version is unsupported
"""
try:
proc = subprocess.Popen(
[self.config.nginx_ctl, "-V"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
text = proc.communicate()[1] # nginx prints output to stderr
except (OSError, ValueError):
raise errors.LetsEncryptConfiguratorError(
"Unable to run %s -V" % self.config.nginx_ctl)
version_regex = re.compile(r"nginx/([0-9\.]*)", re.IGNORECASE)
version_matches = version_regex.findall(text)
sni_regex = re.compile(r"TLS SNI support enabled", re.IGNORECASE)
sni_matches = sni_regex.findall(text)
ssl_regex = re.compile(r" --with-http_ssl_module")
ssl_matches = ssl_regex.findall(text)
if not version_matches:
raise errors.LetsEncryptConfiguratorError(
"Unable to find Nginx version")
if not ssl_matches:
raise errors.LetsEncryptConfiguratorError(
"Nginx build is missing SSL module (--with-http_ssl_module).")
if not sni_matches:
raise errors.LetsEncryptConfiguratorError(
"Nginx build doesn't support SNI")
nginx_version = tuple([int(i) for i in version_matches[0].split(".")])
# nginx < 0.8.21 doesn't use default_server
if nginx_version < (0, 8, 21):
raise errors.LetsEncryptConfiguratorError(
"Nginx version must be 0.8.21+")
return nginx_version
def more_info(self):
"""Human-readable string to help understand the module"""
return (
"Configures Nginx to authenticate and install HTTPS.{0}"
"Server root: {root}{0}"
"Version: {version}".format(
os.linesep, root=self.parser.loc["root"],
version=".".join(str(i) for i in self.version))
)
###################################################
# Wrapper functions for Reverter class (IInstaller)
###################################################
def save(self, title=None, temporary=False):
"""Saves all changes to the configuration files.
:param str title: The title of the save. If a title is given, the
configuration will be saved as a new checkpoint and put in a
timestamped directory.
:param bool temporary: Indicates whether the changes made will
be quickly reversed in the future (ie. challenges)
"""
save_files = set(self.parser.parsed.keys())
# Create Checkpoint
if temporary:
self.reverter.add_to_temp_checkpoint(
save_files, self.save_notes)
else:
self.reverter.add_to_checkpoint(save_files,
self.save_notes)
# Change 'ext' to something else to not override existing conf files
self.parser.filedump(ext='')
if title and not temporary:
self.reverter.finalize_checkpoint(title)
return True
def recovery_routine(self):
"""Revert all previously modified files.
Reverts all modified files that have not been saved as a checkpoint
"""
self.reverter.recovery_routine()
self.parser.load()
def revert_challenge_config(self):
"""Used to cleanup challenge configurations."""
self.reverter.revert_temporary_config()
self.parser.load()
def rollback_checkpoints(self, rollback=1):
"""Rollback saved checkpoints.
:param int rollback: Number of checkpoints to revert
"""
self.reverter.rollback_checkpoints(rollback)
self.parser.load()
def view_config_changes(self):
"""Show all of the configuration changes that have taken place."""
self.reverter.view_config_changes()
###########################################################################
# Challenges Section for IAuthenticator
###########################################################################
def get_chall_pref(self, unused_domain): # pylint: disable=no-self-use
"""Return list of challenge preferences."""
return [challenges.DVSNI]
# Entry point in main.py for performing challenges
def perform(self, achalls):
"""Perform the configuration related challenge.
This function currently assumes all challenges will be fulfilled.
If this turns out not to be the case in the future. Cleanup and
outstanding challenges will have to be designed better.
"""
self._chall_out += len(achalls)
responses = [None] * len(achalls)
nginx_dvsni = dvsni.NginxDvsni(self)
for i, achall in enumerate(achalls):
if isinstance(achall, achallenges.DVSNI):
# Currently also have dvsni hold associated index
# of the challenge. This helps to put all of the responses back
# together when they are all complete.
nginx_dvsni.add_chall(achall, i)
sni_response = nginx_dvsni.perform()
# Must restart in order to activate the challenges.
# Handled here because we may be able to load up other challenge types
self.restart()
# Go through all of the challenges and assign them to the proper place
# in the responses return value. All responses must be in the same order
# as the original challenges.
for i, resp in enumerate(sni_response):
responses[nginx_dvsni.indices[i]] = resp
return responses
# called after challenges are performed
def cleanup(self, achalls):
"""Revert all challenges."""
self._chall_out -= len(achalls)
# If all of the challenges have been finished, clean up everything
if self._chall_out <= 0:
self.revert_challenge_config()
self.restart()
def nginx_restart(nginx_ctl):
"""Restarts the Nginx Server.
:param str nginx_ctl: Path to the Nginx binary.
"""
try:
proc = subprocess.Popen([nginx_ctl, "-s", "reload"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
stdout, stderr = proc.communicate()
if proc.returncode != 0:
# Enter recovery routine...
logging.error("Nginx Restart Failed!")
logging.error(stdout)
logging.error(stderr)
return False
except (OSError, ValueError):
logging.fatal(
"Nginx Restart Failed - Please Check the Configuration")
sys.exit(1)
return True
def temp_install(options_ssl):
"""Temporary install for convenience."""
# WARNING: THIS IS A POTENTIAL SECURITY VULNERABILITY
# THIS SHOULD BE HANDLED BY THE PACKAGE MANAGER
# AND TAKEN OUT BEFORE RELEASE, INSTEAD
# SHOWING A NICE ERROR MESSAGE ABOUT THE PROBLEM.
# Check to make sure options-ssl.conf is installed
if not os.path.isfile(options_ssl):
shutil.copyfile(constants.NGINX_MOD_SSL_CONF, options_ssl)

View File

@@ -0,0 +1,63 @@
"""NginxDVSNI"""
import logging
from letsencrypt.client.plugins.apache.dvsni import ApacheDvsni
class NginxDvsni(ApacheDvsni):
"""Class performs DVSNI challenges within the Nginx configurator.
.. todo:: This is basically copied-and-pasted from the Apache equivalent.
It doesn't actually work yet.
:ivar configurator: NginxConfigurator object
:type configurator: :class:`~nginx.configurator.NginxConfigurator`
:ivar list achalls: Annotated :class:`~letsencrypt.client.achallenges.DVSNI`
challenges.
:param list indices: Meant to hold indices of challenges in a
larger array. NginxDvsni is capable of solving many challenges
at once which causes an indexing issue within NginxConfigurator
who must return all responses in order. Imagine NginxConfigurator
maintaining state about where all of the SimpleHTTPS Challenges,
Dvsni Challenges belong in the response array. This is an optional
utility.
:param str challenge_conf: location of the challenge config file
"""
def perform(self):
"""Perform a DVSNI challenge on Nginx."""
if not self.achalls:
return []
self.configurator.save()
addresses = []
for achall in self.achalls:
vhost = self.configurator.choose_vhost(achall.domain)
if vhost is None:
logging.error(
"No nginx vhost exists with servername or alias of: %s",
achall.domain)
logging.error("No default 443 nginx vhost exists")
logging.error("Please specify servernames in the Nginx config")
return None
else:
addresses.append(list(vhost.addrs))
responses = []
# Create all of the challenge certs
# for achall in self.achalls:
# responses.append(self._setup_challenge_cert(achall))
# Setup the configuration
# self._mod_config(addresses)
# Save reversible changes
self.configurator.save("SNI Challenge", True)
return responses

View File

@@ -0,0 +1,130 @@
"""Very low-level nginx config parser based on pyparsing."""
import string
from pyparsing import (
Literal, White, Word, alphanums, CharsNotIn, Forward, Group,
Optional, OneOrMore, ZeroOrMore, pythonStyleComment)
class RawNginxParser(object):
# pylint: disable=expression-not-assigned
"""A class that parses nginx configuration with pyparsing."""
# constants
left_bracket = Literal("{").suppress()
right_bracket = Literal("}").suppress()
semicolon = Literal(";").suppress()
space = White().suppress()
key = Word(alphanums + "_/")
value = CharsNotIn("{};,")
location = CharsNotIn("{};," + string.whitespace)
# modifier for location uri [ = | ~ | ~* | ^~ ]
modifier = Literal("=") | Literal("~*") | Literal("~") | Literal("^~")
# rules
assignment = (key + Optional(space + value) + semicolon)
block = Forward()
block << Group(
Group(key + Optional(space + modifier) + Optional(space + location))
+ left_bracket
+ Group(ZeroOrMore(Group(assignment) | block))
+ right_bracket)
script = OneOrMore(Group(assignment) | block).ignore(pythonStyleComment)
def __init__(self, source):
self.source = source
def parse(self):
"""Returns the parsed tree."""
return self.script.parseString(self.source)
def as_list(self):
"""Returns the parsed tree as a list."""
return self.parse().asList()
class RawNginxDumper(object):
# pylint: disable=too-few-public-methods
"""A class that dumps nginx configuration from the provided tree."""
def __init__(self, blocks, indentation=4):
self.blocks = blocks
self.indentation = indentation
def __iter__(self, blocks=None, current_indent=0, spacer=' '):
"""Iterates the dumped nginx content."""
blocks = blocks or self.blocks
for key, values in blocks:
if current_indent:
yield spacer
indentation = spacer * current_indent
if isinstance(key, list):
yield indentation + spacer.join(key) + ' {'
for parameter in values:
if isinstance(parameter[0], list):
dumped = self.__iter__(
[parameter],
current_indent + self.indentation)
for line in dumped:
yield line
else:
dumped = spacer.join(parameter) + ';'
yield spacer * (
current_indent + self.indentation) + dumped
yield indentation + '}'
else:
yield spacer * current_indent + key + spacer + values + ';'
def as_string(self):
"""Return the parsed block as a string."""
return '\n'.join(self)
# Shortcut functions to respect Python's serialization interface
# (like pyyaml, picker or json)
def loads(source):
"""Parses from a string.
:param str souce: The string to parse
:returns: The parsed tree
:rtype: list
"""
return RawNginxParser(source).as_list()
def load(_file):
"""Parses from a file.
:param file _file: The file to parse
:returns: The parsed tree
:rtype: list
"""
return loads(_file.read())
def dumps(blocks, indentation=4):
"""Dump to a string.
:param list block: The parsed tree
:param int indentation: The number of spaces to indent
:rtype: str
"""
return RawNginxDumper(blocks, indentation).as_string()
def dump(blocks, _file, indentation=4):
"""Dump to a file.
:param list block: The parsed tree
:param file _file: The file to dump to
:param int indentation: The number of spaces to indent
:rtype: NoneType
"""
return _file.write(dumps(blocks, indentation))

View File

@@ -0,0 +1,125 @@
"""Module contains classes used by the Nginx Configurator."""
import re
from letsencrypt.client.plugins.apache.obj import Addr as ApacheAddr
class Addr(ApacheAddr):
"""Represents an Nginx address, i.e. what comes after the 'listen'
directive.
According to http://nginx.org/en/docs/http/ngx_http_core_module.html#listen,
this may be address[:port], port, or unix:path. The latter is ignored here.
The default value if no directive is specified is *:80 (superuser) or
*:8000 (otherwise). If no port is specified, the default is 80. If no
address is specified, listen on all addresses.
.. todo:: Old-style nginx configs define SSL vhosts in a separate block
instead of using 'ssl' in the listen directive
:param str addr: addr part of vhost address, may be hostname, IPv4, IPv6,
"", or "*"
:param str port: port number or "*" or ""
:param bool ssl: Whether the directive includes 'ssl'
:param bool default: Whether the directive includes 'default_server'
"""
def __init__(self, host, port, ssl, default):
super(Addr, self).__init__((host, port))
self.ssl = ssl
self.default = default
@classmethod
def fromstring(cls, str_addr):
"""Initialize Addr from string."""
parts = str_addr.split(' ')
ssl = False
default = False
host = ''
port = ''
# The first part must be the address
addr = parts.pop(0)
# Ignore UNIX-domain sockets
if addr.startswith('unix:'):
return None
tup = addr.partition(':')
if re.match(r'^\d+$', tup[0]):
# This is a bare port, not a hostname. E.g. listen 80
host = ''
port = tup[0]
else:
# This is a host-port tuple. E.g. listen 127.0.0.1:*
host = tup[0]
port = tup[2]
# The rest of the parts are options; we only care about ssl and default
while len(parts) > 0:
nextpart = parts.pop()
if nextpart == 'ssl':
ssl = True
elif nextpart == 'default_server':
default = True
return cls(host, port, ssl, default)
def __str__(self):
if self.tup[0] and self.tup[1]:
return "%s:%s" % self.tup
elif self.tup[0]:
return self.tup[0]
else:
return self.tup[1]
def __eq__(self, other):
if isinstance(other, self.__class__):
return (self.tup == other.tup and
self.ssl == other.ssl and
self.default == other.default)
return False
class VirtualHost(object): # pylint: disable=too-few-public-methods
"""Represents an Nginx Virtualhost.
:ivar str filep: file path of VH
:ivar set addrs: Virtual Host addresses (:class:`set` of :class:`Addr`)
:ivar set names: Server names/aliases of vhost
(:class:`list` of :class:`str`)
:ivar array raw: The raw form of the parsed server block
:ivar bool ssl: SSLEngine on in vhost
:ivar bool enabled: Virtual host is enabled
"""
def __init__(self, filep, addrs, ssl, enabled, names, raw):
# pylint: disable=too-many-arguments
"""Initialize a VH."""
self.filep = filep
self.addrs = addrs
self.names = names
self.ssl = ssl
self.enabled = enabled
self.raw = raw
def __str__(self):
addr_str = ", ".join(str(addr) for addr in self.addrs)
return ("file: %s\n"
"addrs: %s\n"
"names: %s\n"
"ssl: %s\n"
"enabled: %s" % (self.filep, addr_str,
self.names, self.ssl, self.enabled))
def __eq__(self, other):
if isinstance(other, self.__class__):
return (self.filep == other.filep and
list(self.addrs) == list(other.addrs) and
self.names == other.names and
self.ssl == other.ssl and self.enabled == other.enabled)
return False

View File

@@ -0,0 +1,8 @@
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 1440m;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
# Using list of ciphers from "Bulletproof SSL and TLS"
ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256 ECDHE-ECDSA-AES256-GCM-SHA384 ECDHE-ECDSA-AES128-SHA ECDHE-ECDSA-AES256-SHA ECDHE-ECDSA-AES128-SHA256 ECDHE-ECDSA-AES256-SHA384 ECDHE-RSA-AES128-GCM-SHA256 ECDHE-RSA-AES256-GCM-SHA384 ECDHE-RSA-AES128-SHA ECDHE-RSA-AES128-SHA256 ECDHE-RSA-AES256-SHA384 DHE-RSA-AES128-GCM-SHA256 DHE-RSA-AES256-GCM-SHA384 DHE-RSA-AES128-SHA DHE-RSA-AES256-SHA DHE-RSA-AES128-SHA256 DHE-RSA-AES256-SHA256 EDH-RSA-DES-CBC3-SHA";

View File

@@ -0,0 +1,484 @@
"""NginxParser is a member object of the NginxConfigurator class."""
import glob
import logging
import os
import pyparsing
import re
from letsencrypt.client import errors
from letsencrypt.client.plugins.nginx import obj
from letsencrypt.client.plugins.nginx.nginxparser import dump, load
class NginxParser(object):
"""Class handles the fine details of parsing the Nginx Configuration.
:ivar str root: Normalized abosulte path to the server root
directory. Without trailing slash.
:ivar dict parsed: Mapping of file paths to parsed trees
"""
def __init__(self, root, ssl_options):
self.parsed = {}
self.root = os.path.abspath(root)
self.loc = self._set_locations(ssl_options)
# Parse nginx.conf and included files.
# TODO: Check sites-available/ as well. For now, the configurator does
# not enable sites from there.
self.load()
def load(self):
"""Loads Nginx files into a parsed tree.
"""
self._parse_recursively(self.loc["root"])
def _parse_recursively(self, filepath):
"""Parses nginx config files recursively by looking at 'include'
directives inside 'http' and 'server' blocks. Note that this only
reads Nginx files that potentially declare a virtual host.
.. todo:: Can Nginx 'virtual hosts' be defined somewhere other than in
the server context?
:param str filepath: The path to the files to parse, as a glob
"""
filepath = self.abs_path(filepath)
trees = self._parse_files(filepath)
for tree in trees:
for entry in tree:
if _is_include_directive(entry):
# Parse the top-level included file
self._parse_recursively(entry[1])
elif entry[0] == ['http'] or entry[0] == ['server']:
# Look for includes in the top-level 'http'/'server' context
for subentry in entry[1]:
if _is_include_directive(subentry):
self._parse_recursively(subentry[1])
elif entry[0] == ['http'] and subentry[0] == ['server']:
# Look for includes in a 'server' context within
# an 'http' context
for server_entry in subentry[1]:
if _is_include_directive(server_entry):
self._parse_recursively(server_entry[1])
def abs_path(self, path):
"""Converts a relative path to an absolute path relative to the root.
Does nothing for paths that are already absolute.
:param str path: The path
:returns: The absolute path
:rtype: str
"""
if not os.path.isabs(path):
return os.path.join(self.root, path)
else:
return path
def get_vhosts(self):
# pylint: disable=cell-var-from-loop
"""Gets list of all 'virtual hosts' found in Nginx configuration.
Technically this is a misnomer because Nginx does not have virtual
hosts, it has 'server blocks'.
:returns: List of
:class:`~letsencrypt.client.plugins.nginx.obj.VirtualHost` objects
found in configuration
:rtype: list
"""
enabled = True # We only look at enabled vhosts for now
vhosts = []
servers = {}
for filename in self.parsed:
tree = self.parsed[filename]
servers[filename] = []
srv = servers[filename] # workaround undefined loop var in lambdas
# Find all the server blocks
_do_for_subarray(tree, lambda x: x[0] == ['server'],
lambda x: srv.append(x[1]))
# Find 'include' statements in server blocks and append their trees
for i, server in enumerate(servers[filename]):
new_server = self._get_included_directives(server)
servers[filename][i] = new_server
for filename in servers:
for server in servers[filename]:
# Parse the server block into a VirtualHost object
parsed_server = _parse_server(server)
vhost = obj.VirtualHost(filename,
parsed_server['addrs'],
parsed_server['ssl'],
enabled,
parsed_server['names'],
server)
vhosts.append(vhost)
return vhosts
def _get_included_directives(self, block):
"""Returns array with the "include" directives expanded out by
concatenating the contents of the included file to the block.
:param list block:
:rtype: list
"""
result = list(block) # Copy the list to keep self.parsed idempotent
for directive in block:
if _is_include_directive(directive):
included_files = glob.glob(
self.abs_path(directive[1]))
for incl in included_files:
try:
result.extend(self.parsed[incl])
except KeyError:
pass
return result
def _parse_files(self, filepath, override=False):
"""Parse files from a glob
:param str filepath: Nginx config file path
:param bool override: Whether to parse a file that has been parsed
:returns: list of parsed tree structures
:rtype: list
"""
files = glob.glob(filepath)
trees = []
for item in files:
if item in self.parsed and not override:
continue
try:
with open(item) as _file:
parsed = load(_file)
self.parsed[item] = parsed
trees.append(parsed)
except IOError:
logging.warn("Could not open file: %s", item)
except pyparsing.ParseException:
logging.warn("Could not parse file: %s", item)
return trees
def _set_locations(self, ssl_options):
"""Set default location for directives.
Locations are given as file_paths
.. todo:: Make sure that files are included
"""
root = self._find_config_root()
default = root
nginx_temp = os.path.join(self.root, "nginx_ports.conf")
if os.path.isfile(nginx_temp):
listen = nginx_temp
name = nginx_temp
else:
listen = default
name = default
return {"root": root, "default": default, "listen": listen,
"name": name, "ssl_options": ssl_options}
def _find_config_root(self):
"""Find the Nginx Configuration Root file."""
location = ['nginx.conf']
for name in location:
if os.path.isfile(os.path.join(self.root, name)):
return os.path.join(self.root, name)
raise errors.LetsEncryptNoInstallationError(
"Could not find configuration root")
def filedump(self, ext='tmp'):
"""Dumps parsed configurations into files.
:param str ext: The file extension to use for the dumped files. If
empty, this overrides the existing conf files.
"""
for filename in self.parsed:
tree = self.parsed[filename]
if ext:
filename = filename + os.path.extsep + ext
try:
with open(filename, 'w') as _file:
dump(tree, _file)
except IOError:
logging.error("Could not open file for writing: %s", filename)
def _has_server_names(self, entry, names):
"""Checks if a server block has the given set of server_names. This
is the primary way of identifying server blocks in the configurator.
Returns false if 'entry' doesn't look like a server block at all.
..todo :: Doesn't match server blocks whose server_name directives are
split across multiple conf files.
:param list entry: The block to search
:param set names: The names to match
:rtype: bool
"""
if len(names) == 0:
# Nothing to identify blocks with
return False
if not isinstance(entry, list):
# Can't be a server block
return False
new_entry = self._get_included_directives(entry)
server_names = set()
for item in new_entry:
if not isinstance(item, list):
# Can't be a server block
return False
if item[0] == 'server_name':
server_names.update(_get_servernames(item[1]))
return server_names == names
def add_server_directives(self, filename, names, directives,
replace=False):
"""Add or replace directives in server blocks whose server_name set
is 'names'. If replace is True, this raises a misconfiguration error
if the directive does not already exist.
..todo :: Doesn't match server blocks whose server_name directives are
split across multiple conf files.
:param str filename: The absolute filename of the config file
:param set names: The server_name to match
:param list directives: The directives to add
:param bool replace: Whether to only replace existing directives
"""
if replace:
_do_for_subarray(self.parsed[filename],
lambda x: self._has_server_names(x, names),
lambda x: _replace_directives(x, directives))
else:
_do_for_subarray(self.parsed[filename],
lambda x: self._has_server_names(x, names),
lambda x: x.extend(directives))
def get_all_certs_keys(self):
"""Gets all certs and keys in the nginx config.
:returns: list of tuples with form [(cert, key, path)]
cert - str path to certificate file
key - str path to associated key file
path - File path to configuration file.
:rtype: set
"""
c_k = set()
vhosts = self.get_vhosts()
for vhost in vhosts:
tup = [None, None, vhost.filep]
if vhost.ssl:
for directive in vhost.raw:
if directive[0] == 'ssl_certificate':
tup[0] = directive[1]
elif directive[0] == 'ssl_certificate_key':
tup[1] = directive[1]
if tup[0] is not None and tup[1] is not None:
c_k.add(tuple(tup))
return c_k
def _do_for_subarray(entry, condition, func):
"""Executes a function for a subarray of a nested array if it matches
the given condition.
:param list entry: The list to iterate over
:param function condition: Returns true iff func should be executed on item
:param function func: The function to call for each matching item
"""
if isinstance(entry, list):
if condition(entry):
func(entry)
else:
for item in entry:
_do_for_subarray(item, condition, func)
def get_best_match(target_name, names):
"""Finds the best match for target_name out of names using the Nginx
name-matching rules (exact > longest wildcard starting with * >
longest wildcard ending with * > regex).
:param str target_name: The name to match
:param set names: The candidate server names
:returns: Tuple of (type of match, the name that matched)
:rtype: tuple
"""
exact = []
wildcard_start = []
wildcard_end = []
regex = []
for name in names:
if _exact_match(target_name, name):
exact.append(name)
elif _wildcard_match(target_name, name, True):
wildcard_start.append(name)
elif _wildcard_match(target_name, name, False):
wildcard_end.append(name)
elif _regex_match(target_name, name):
regex.append(name)
if len(exact) > 0:
# There can be more than one exact match; e.g. eff.org, .eff.org
match = min(exact, key=len)
return ('exact', match)
if len(wildcard_start) > 0:
# Return the longest wildcard
match = max(wildcard_start, key=len)
return ('wildcard_start', match)
if len(wildcard_end) > 0:
# Return the longest wildcard
match = max(wildcard_end, key=len)
return ('wildcard_end', match)
if len(regex) > 0:
# Just return the first one for now
match = regex[0]
return ('regex', match)
return (None, None)
def _exact_match(target_name, name):
return target_name == name or '.' + target_name == name
def _wildcard_match(target_name, name, start):
# Degenerate case
if name == '*':
return True
parts = target_name.split('.')
match_parts = name.split('.')
# If the domain ends in a wildcard, do the match procedure in reverse
if not start:
parts.reverse()
match_parts.reverse()
if len(match_parts) == 0:
return False
# The first part must be a wildcard or blank, e.g. '.eff.org'
first = match_parts.pop(0)
if first != '*' and first != '':
return False
target_name = '.'.join(parts)
name = '.'.join(match_parts)
# Ex: www.eff.org matches *.eff.org, eff.org does not match *.eff.org
return target_name.endswith('.' + name)
def _regex_match(target_name, name):
# Must start with a tilde
if len(name) < 2 or name[0] != '~':
return False
# After tilde is a perl-compatible regex
try:
regex = re.compile(name[1:])
if re.match(regex, target_name):
return True
else:
return False
except re.error:
# perl-compatible regexes are sometimes not recognized by python
return False
def _is_include_directive(entry):
"""Checks if an nginx parsed entry is an 'include' directive.
:param list entry: the parsed entry
:returns: Whether it's an 'include' directive
:rtype: bool
"""
return (isinstance(entry, list) and
entry[0] == 'include' and len(entry) == 2 and
isinstance(entry[1], str))
def _get_servernames(names):
"""Turns a server_name string into a list of server names
:param str names: server names
:rtype: list
"""
whitespace_re = re.compile(r'\s+')
names = re.sub(whitespace_re, ' ', names)
return names.split(' ')
def _parse_server(server):
"""Parses a list of server directives.
:param list server: list of directives in a server block
:rtype: dict
"""
parsed_server = {}
parsed_server['addrs'] = set()
parsed_server['ssl'] = False
parsed_server['names'] = set()
for directive in server:
if directive[0] == 'listen':
addr = obj.Addr.fromstring(directive[1])
parsed_server['addrs'].add(addr)
if not parsed_server['ssl'] and addr.ssl:
parsed_server['ssl'] = True
elif directive[0] == 'server_name':
parsed_server['names'].update(
_get_servernames(directive[1]))
return parsed_server
def _replace_directives(block, directives):
"""Replaces directives in a block. If the directive doesn't exist in
the entry already, raises a misconfiguration error.
..todo :: Find directives that are in included files.
:param list block: The block to replace in
:param list directives: The new directives.
"""
for directive in directives:
changed = False
if len(directive) == 0:
continue
for index, line in enumerate(block):
if len(line) > 0 and line[0] == directive[0]:
block[index] = directive
changed = True
if not changed:
raise errors.LetsEncryptMisconfigurationError(
'LetsEncrypt expected directive for %s in the Nginx config '
'but did not find it.' % directive[0])

View File

@@ -0,0 +1 @@
"""Let's Encrypt Nginx Tests"""

View File

@@ -0,0 +1,264 @@
"""Test for letsencrypt.client.plugins.nginx.configurator."""
import shutil
import unittest
import mock
from letsencrypt.acme import challenges
from letsencrypt.client import achallenges
from letsencrypt.client import errors
from letsencrypt.client import le_util
from letsencrypt.client.plugins.nginx.tests import util
class NginxConfiguratorTest(util.NginxTest):
"""Test a semi complex vhost configuration."""
def setUp(self):
super(NginxConfiguratorTest, self).setUp()
self.config = util.get_nginx_configurator(
self.config_path, self.config_dir, self.work_dir,
self.ssl_options)
def tearDown(self):
shutil.rmtree(self.temp_dir)
shutil.rmtree(self.config_dir)
shutil.rmtree(self.work_dir)
def test_prepare(self):
self.assertEquals((1, 6, 2), self.config.version)
self.assertEquals(5, len(self.config.parser.parsed))
def test_get_all_names(self):
names = self.config.get_all_names()
self.assertEqual(names, set(
["*.www.foo.com", "somename", "another.alias",
"alias", "localhost", ".example.com", r"~^(www\.)?(example|bar)\.",
"155.225.50.69.nephoscale.net", "*.www.example.com",
"example.*", "www.example.org", "myhost"]))
def test_supported_enhancements(self):
self.assertEqual([], self.config.supported_enhancements())
def test_enhance(self):
self.assertRaises(errors.LetsEncryptConfiguratorError,
self.config.enhance,
'myhost',
'redirect')
def test_get_chall_pref(self):
self.assertEqual([challenges.DVSNI],
self.config.get_chall_pref('myhost'))
def test_save(self):
filep = self.config.parser.abs_path('sites-enabled/example.com')
self.config.parser.add_server_directives(
filep, set(['.example.com', 'example.*']),
[['listen', '443 ssl']])
self.config.save()
# pylint: disable=protected-access
parsed = self.config.parser._parse_files(filep, override=True)
self.assertEqual([[['server'], [['listen', '69.50.225.155:9000'],
['listen', '127.0.0.1'],
['server_name', '.example.com'],
['server_name', 'example.*'],
['listen', '443 ssl']]]],
parsed[0])
def test_choose_vhost(self):
localhost_conf = set(['localhost', r'~^(www\.)?(example|bar)\.'])
server_conf = set(['somename', 'another.alias', 'alias'])
example_conf = set(['.example.com', 'example.*'])
foo_conf = set(['*.www.foo.com', '*.www.example.com'])
results = {'localhost': localhost_conf,
'alias': server_conf,
'example.com': example_conf,
'example.com.uk.test': example_conf,
'www.example.com': example_conf,
'test.www.example.com': foo_conf,
'abc.www.foo.com': foo_conf,
'www.bar.co.uk': localhost_conf}
bad_results = ['www.foo.com', 'example', 't.www.bar.co',
'69.255.225.155']
for name in results:
self.assertEqual(results[name],
self.config.choose_vhost(name).names)
for name in bad_results:
self.assertEqual(None, self.config.choose_vhost(name))
def test_more_info(self):
self.assertTrue('nginx.conf' in self.config.more_info())
def test_deploy_cert(self):
server_conf = self.config.parser.abs_path('server.conf')
nginx_conf = self.config.parser.abs_path('nginx.conf')
example_conf = self.config.parser.abs_path('sites-enabled/example.com')
# Get the default 443 vhost
self.config.deploy_cert(
"www.example.com",
"example/cert.pem", "example/key.pem")
self.config.deploy_cert(
"another.alias",
"/etc/nginx/cert.pem", "/etc/nginx/key.pem")
self.config.save()
self.config.parser.load()
self.assertEqual([[['server'],
[['listen', '69.50.225.155:9000'],
['listen', '127.0.0.1'],
['server_name', '.example.com'],
['server_name', 'example.*'],
['listen', '443 ssl'],
['ssl_certificate', 'example/cert.pem'],
['ssl_certificate_key', 'example/key.pem'],
['include',
self.config.parser.loc["ssl_options"]]]]],
self.config.parser.parsed[example_conf])
self.assertEqual([['server_name', 'somename alias another.alias']],
self.config.parser.parsed[server_conf])
self.assertEqual([['server'],
[['listen', '8000'],
['listen', 'somename:8080'],
['include', 'server.conf'],
[['location', '/'],
[['root', 'html'],
['index', 'index.html index.htm']]],
['listen', '443 ssl'],
['ssl_certificate', '/etc/nginx/cert.pem'],
['ssl_certificate_key', '/etc/nginx/key.pem'],
['include',
self.config.parser.loc["ssl_options"]]]],
self.config.parser.parsed[nginx_conf][-1][-1][-1])
def test_get_all_certs_keys(self):
nginx_conf = self.config.parser.abs_path('nginx.conf')
example_conf = self.config.parser.abs_path('sites-enabled/example.com')
# Get the default 443 vhost
self.config.deploy_cert(
"www.example.com",
"example/cert.pem", "example/key.pem")
self.config.deploy_cert(
"another.alias",
"/etc/nginx/cert.pem", "/etc/nginx/key.pem")
self.config.save()
self.config.parser.load()
self.assertEqual(set([
('example/cert.pem', 'example/key.pem', example_conf),
('/etc/nginx/cert.pem', '/etc/nginx/key.pem', nginx_conf),
]), self.config.get_all_certs_keys())
@mock.patch("letsencrypt.client.plugins.nginx.configurator."
"dvsni.NginxDvsni.perform")
@mock.patch("letsencrypt.client.plugins.nginx.configurator."
"NginxConfigurator.restart")
def test_perform(self, mock_restart, mock_dvsni_perform):
# Only tests functionality specific to configurator.perform
# Note: As more challenges are offered this will have to be expanded
auth_key = le_util.Key(self.rsa256_file, self.rsa256_pem)
achall1 = achallenges.DVSNI(
chall=challenges.DVSNI(
r="foo",
nonce="bar"),
domain="localhost", key=auth_key)
achall2 = achallenges.DVSNI(
chall=challenges.DVSNI(
r="abc",
nonce="def"),
domain="example.com", key=auth_key)
dvsni_ret_val = [
challenges.DVSNIResponse(s="irrelevant"),
challenges.DVSNIResponse(s="arbitrary"),
]
mock_dvsni_perform.return_value = dvsni_ret_val
responses = self.config.perform([achall1, achall2])
self.assertEqual(mock_dvsni_perform.call_count, 1)
self.assertEqual(responses, dvsni_ret_val)
self.assertEqual(mock_restart.call_count, 1)
@mock.patch("letsencrypt.client.plugins.nginx.configurator."
"subprocess.Popen")
def test_get_version(self, mock_popen):
mock_popen().communicate.return_value = (
"", "\n".join(["nginx version: nginx/1.4.2",
"built by clang 6.0 (clang-600.0.56)"
" (based on LLVM 3.5svn)",
"TLS SNI support enabled",
"configure arguments: --prefix=/usr/local/Cellar/"
"nginx/1.6.2 --with-http_ssl_module"]))
self.assertEqual(self.config.get_version(), (1, 4, 2))
mock_popen().communicate.return_value = (
"", "\n".join(["nginx version: nginx/0.9",
"built by clang 6.0 (clang-600.0.56)"
" (based on LLVM 3.5svn)",
"TLS SNI support enabled",
"configure arguments: --with-http_ssl_module"]))
self.assertEqual(self.config.get_version(), (0, 9))
mock_popen().communicate.return_value = (
"", "\n".join(["blah 0.0.1",
"built by clang 6.0 (clang-600.0.56)"
" (based on LLVM 3.5svn)",
"TLS SNI support enabled",
"configure arguments: --with-http_ssl_module"]))
self.assertRaises(errors.LetsEncryptConfiguratorError,
self.config.get_version)
mock_popen().communicate.return_value = (
"", "\n".join(["nginx version: nginx/1.4.2",
"TLS SNI support enabled"]))
self.assertRaises(errors.LetsEncryptConfiguratorError,
self.config.get_version)
mock_popen().communicate.return_value = (
"", "\n".join(["nginx version: nginx/1.4.2",
"built by clang 6.0 (clang-600.0.56)"
" (based on LLVM 3.5svn)",
"configure arguments: --with-http_ssl_module"]))
self.assertRaises(errors.LetsEncryptConfiguratorError,
self.config.get_version)
mock_popen().communicate.return_value = (
"", "\n".join(["nginx version: nginx/0.8.1",
"built by clang 6.0 (clang-600.0.56)"
" (based on LLVM 3.5svn)",
"TLS SNI support enabled",
"configure arguments: --with-http_ssl_module"]))
self.assertRaises(errors.LetsEncryptConfiguratorError,
self.config.get_version)
mock_popen.side_effect = OSError("Can't find program")
self.assertRaises(
errors.LetsEncryptConfiguratorError, self.config.get_version)
@mock.patch("letsencrypt.client.plugins.nginx.configurator."
"subprocess.Popen")
def test_nginx_restart(self, mock_popen):
mocked = mock_popen()
mocked.communicate.return_value = ('', '')
mocked.returncode = 0
self.assertTrue(self.config.restart())
@mock.patch("letsencrypt.client.plugins.nginx.configurator."
"subprocess.Popen")
def test_config_test(self, mock_popen):
mocked = mock_popen()
mocked.communicate.return_value = ('', '')
mocked.returncode = 0
self.assertTrue(self.config.config_test())
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,85 @@
"""Test for letsencrypt.client.plugins.nginx.dvsni."""
import pkg_resources
import unittest
import shutil
import mock
from letsencrypt.acme import challenges
from letsencrypt.client import achallenges
from letsencrypt.client import le_util
from letsencrypt.client.plugins.nginx.tests import util
class DvsniPerformTest(util.NginxTest):
"""Test the NginxDVSNI challenge."""
def setUp(self):
super(DvsniPerformTest, self).setUp()
config = util.get_nginx_configurator(
self.config_path, self.config_dir, self.work_dir,
self.ssl_options)
rsa256_file = pkg_resources.resource_filename(
"letsencrypt.client.tests", "testdata/rsa256_key.pem")
rsa256_pem = pkg_resources.resource_string(
"letsencrypt.client.tests", "testdata/rsa256_key.pem")
auth_key = le_util.Key(rsa256_file, rsa256_pem)
from letsencrypt.client.plugins.nginx import dvsni
self.sni = dvsni.NginxDvsni(config)
self.achalls = [
achallenges.DVSNI(
chall=challenges.DVSNI(
r="foo",
nonce="bar",
), domain="www.example.com", key=auth_key),
achallenges.DVSNI(
chall=challenges.DVSNI(
r="\xba\xa9\xda?<m\xaewmx\xea\xad\xadv\xf4\x02\xc9y\x80"
"\xe2_X\t\xe7\xc7\xa4\t\xca\xf7&\x945",
nonce="Y\xed\x01L\xac\x95\xf7pW\xb1\xd7"
"\xa1\xb2\xc5\x96\xba",
), domain="blah", key=auth_key),
]
def tearDown(self):
shutil.rmtree(self.temp_dir)
shutil.rmtree(self.config_dir)
shutil.rmtree(self.work_dir)
def test_add_chall(self):
self.sni.add_chall(self.achalls[0], 0)
self.assertEqual(1, len(self.sni.achalls))
self.assertEqual([0], self.sni.indices)
@mock.patch("letsencrypt.client.plugins.nginx.configurator."
"NginxConfigurator.save")
def test_perform0(self, mock_save):
self.sni.add_chall(self.achalls[0])
responses = self.sni.perform()
self.assertEqual([], responses)
self.assertEqual(mock_save.call_count, 2)
def test_setup_challenge_cert(self):
# This is a helper function that can be used for handling
# open context managers more elegantly. It avoids dealing with
# __enter__ and __exit__ calls.
# http://www.voidspace.org.uk/python/mock/helpers.html#mock.mock_open
pass
@mock.patch("letsencrypt.client.plugins.nginx.configurator."
"NginxConfigurator.save")
def test_perform1(self, mock_save):
self.sni.add_chall(self.achalls[1])
responses = self.sni.perform()
self.assertEqual(None, responses)
self.assertEqual(mock_save.call_count, 1)
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,108 @@
"""Test for letsencrypt.client.plugins.nginx.nginxparser."""
import operator
import unittest
from letsencrypt.client.plugins.nginx.nginxparser import (RawNginxParser,
load, dumps, dump)
from letsencrypt.client.plugins.nginx.tests import util
FIRST = operator.itemgetter(0)
class TestRawNginxParser(unittest.TestCase):
"""Test the raw low-level Nginx config parser."""
def test_assignments(self):
parsed = RawNginxParser.assignment.parseString('root /test;').asList()
self.assertEqual(parsed, ['root', '/test'])
parsed = RawNginxParser.assignment.parseString('root /test;'
'foo bar;').asList()
self.assertEqual(parsed, ['root', '/test'], ['foo', 'bar'])
def test_blocks(self):
parsed = RawNginxParser.block.parseString('foo {}').asList()
self.assertEqual(parsed, [[['foo'], []]])
parsed = RawNginxParser.block.parseString('location /foo{}').asList()
self.assertEqual(parsed, [[['location', '/foo'], []]])
parsed = RawNginxParser.block.parseString('foo { bar foo; }').asList()
self.assertEqual(parsed, [[['foo'], [['bar', 'foo']]]])
def test_nested_blocks(self):
parsed = RawNginxParser.block.parseString('foo { bar {} }').asList()
block, content = FIRST(parsed)
self.assertEqual(FIRST(content), [['bar'], []])
self.assertEqual(FIRST(block), 'foo')
def test_dump_as_string(self):
dumped = dumps([
['user', 'www-data'],
[['server'], [
['listen', '80'],
['server_name', 'foo.com'],
['root', '/home/ubuntu/sites/foo/'],
[['location', '/status'], [
['check_status'],
[['types'], [['image/jpeg', 'jpg']]],
]]
]]])
self.assertEqual(dumped,
'user www-data;\n'
'server {\n'
' listen 80;\n'
' server_name foo.com;\n'
' root /home/ubuntu/sites/foo/;\n \n'
' location /status {\n'
' check_status;\n \n'
' types {\n'
' image/jpeg jpg;\n'
' }\n'
' }\n'
'}')
def test_parse_from_file(self):
parsed = load(open(util.get_data_filename('foo.conf')))
self.assertEqual(
parsed,
[['user', 'www-data'],
[['http'],
[[['server'], [
['listen', '*:80 default_server ssl'],
['server_name', '*.www.foo.com *.www.example.com'],
['root', '/home/ubuntu/sites/foo/'],
[['location', '/status'], [
[['types'], [['image/jpeg', 'jpg']]],
]],
[['location', '~', r'case_sensitive\.php$'], [
['index', 'index.php'],
['root', '/var/root'],
]],
[['location', '~*', r'case_insensitive\.php$'], []],
[['location', '=', r'exact_match\.php$'], []],
[['location', '^~', r'ignore_regex\.php$'], []]
]]]]]
)
def test_dump_as_file(self):
parsed = load(open(util.get_data_filename('nginx.conf')))
parsed[-1][-1].append([['server'],
[['listen', '443 ssl'],
['server_name', 'localhost'],
['ssl_certificate', 'cert.pem'],
['ssl_certificate_key', 'cert.key'],
['ssl_session_cache', 'shared:SSL:1m'],
['ssl_session_timeout', '5m'],
['ssl_ciphers', 'HIGH:!aNULL:!MD5'],
[['location', '/'],
[['root', 'html'],
['index', 'index.html index.htm']]]]])
_file = open(util.get_data_filename('nginx.new.conf'), 'w')
dump(parsed, _file)
_file.close()
parsed_new = load(open(util.get_data_filename('nginx.new.conf')))
self.assertEquals(parsed, parsed_new)
if __name__ == '__main__':
unittest.main()

View File

@@ -0,0 +1,105 @@
"""Test the helper objects in letsencrypt.client.plugins.nginx.obj."""
import unittest
class AddrTest(unittest.TestCase):
"""Test the Addr class."""
def setUp(self):
from letsencrypt.client.plugins.nginx.obj import Addr
self.addr1 = Addr.fromstring("192.168.1.1")
self.addr2 = Addr.fromstring("192.168.1.1:* ssl")
self.addr3 = Addr.fromstring("192.168.1.1:80")
self.addr4 = Addr.fromstring("*:80 default_server ssl")
self.addr5 = Addr.fromstring("myhost")
self.addr6 = Addr.fromstring("80 default_server spdy")
self.addr7 = Addr.fromstring("unix:/var/run/nginx.sock")
def test_fromstring(self):
self.assertEqual(self.addr1.get_addr(), "192.168.1.1")
self.assertEqual(self.addr1.get_port(), "")
self.assertFalse(self.addr1.ssl)
self.assertFalse(self.addr1.default)
self.assertEqual(self.addr2.get_addr(), "192.168.1.1")
self.assertEqual(self.addr2.get_port(), "*")
self.assertTrue(self.addr2.ssl)
self.assertFalse(self.addr2.default)
self.assertEqual(self.addr3.get_addr(), "192.168.1.1")
self.assertEqual(self.addr3.get_port(), "80")
self.assertFalse(self.addr3.ssl)
self.assertFalse(self.addr3.default)
self.assertEqual(self.addr4.get_addr(), "*")
self.assertEqual(self.addr4.get_port(), "80")
self.assertTrue(self.addr4.ssl)
self.assertTrue(self.addr4.default)
self.assertEqual(self.addr5.get_addr(), "myhost")
self.assertEqual(self.addr5.get_port(), "")
self.assertFalse(self.addr5.ssl)
self.assertFalse(self.addr5.default)
self.assertEqual(self.addr6.get_addr(), "")
self.assertEqual(self.addr6.get_port(), "80")
self.assertFalse(self.addr6.ssl)
self.assertTrue(self.addr6.default)
self.assertEqual(None, self.addr7)
def test_str(self):
self.assertEqual(str(self.addr1), "192.168.1.1")
self.assertEqual(str(self.addr2), "192.168.1.1:*")
self.assertEqual(str(self.addr3), "192.168.1.1:80")
self.assertEqual(str(self.addr4), "*:80")
self.assertEqual(str(self.addr5), "myhost")
self.assertEqual(str(self.addr6), "80")
def test_eq(self):
from letsencrypt.client.plugins.nginx.obj import Addr
new_addr1 = Addr.fromstring("192.168.1.1 spdy")
self.assertEqual(self.addr1, new_addr1)
self.assertNotEqual(self.addr1, self.addr2)
self.assertFalse(self.addr1 == 3333)
def test_set_inclusion(self):
from letsencrypt.client.plugins.nginx.obj import Addr
set_a = set([self.addr1, self.addr2])
addr1b = Addr.fromstring("192.168.1.1")
addr2b = Addr.fromstring("192.168.1.1:* ssl")
set_b = set([addr1b, addr2b])
self.assertEqual(set_a, set_b)
class VirtualHostTest(unittest.TestCase):
"""Test the VirtualHost class."""
def setUp(self):
from letsencrypt.client.plugins.nginx.obj import VirtualHost
from letsencrypt.client.plugins.nginx.obj import Addr
self.vhost1 = VirtualHost(
"filep",
set([Addr.fromstring("localhost")]), False, False,
set(['localhost']), [])
def test_eq(self):
from letsencrypt.client.plugins.nginx.obj import Addr
from letsencrypt.client.plugins.nginx.obj import VirtualHost
vhost1b = VirtualHost(
"filep",
set([Addr.fromstring("localhost blah")]), False, False,
set(['localhost']), [])
self.assertEqual(vhost1b, self.vhost1)
self.assertEqual(str(vhost1b), str(self.vhost1))
self.assertFalse(vhost1b == 1234)
def test_str(self):
stringified = '\n'.join(['file: filep', 'addrs: localhost',
"names: set(['localhost'])", 'ssl: False',
'enabled: False'])
self.assertEqual(stringified, str(self.vhost1))
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,206 @@
"""Tests for letsencrypt.client.plugins.nginx.parser."""
import glob
import os
import re
import shutil
import unittest
from letsencrypt.client.errors import LetsEncryptMisconfigurationError
from letsencrypt.client.plugins.nginx import nginxparser
from letsencrypt.client.plugins.nginx import obj
from letsencrypt.client.plugins.nginx import parser
from letsencrypt.client.plugins.nginx.tests import util
class NginxParserTest(util.NginxTest):
"""Nginx Parser Test."""
def setUp(self):
super(NginxParserTest, self).setUp()
def tearDown(self):
shutil.rmtree(self.temp_dir)
shutil.rmtree(self.config_dir)
shutil.rmtree(self.work_dir)
def test_root_normalized(self):
path = os.path.join(self.temp_dir, "foo/////"
"bar/../../testdata")
nparser = parser.NginxParser(path, None)
self.assertEqual(nparser.root, self.config_path)
def test_root_absolute(self):
nparser = parser.NginxParser(os.path.relpath(self.config_path), None)
self.assertEqual(nparser.root, self.config_path)
def test_root_no_trailing_slash(self):
nparser = parser.NginxParser(self.config_path + os.path.sep, None)
self.assertEqual(nparser.root, self.config_path)
def test_load(self):
"""Test recursive conf file parsing.
"""
nparser = parser.NginxParser(self.config_path, self.ssl_options)
nparser.load()
self.assertEqual(set([nparser.abs_path(x) for x in
['foo.conf', 'nginx.conf', 'server.conf',
'sites-enabled/default',
'sites-enabled/example.com']]),
set(nparser.parsed.keys()))
self.assertEqual([['server_name', 'somename alias another.alias']],
nparser.parsed[nparser.abs_path('server.conf')])
self.assertEqual([[['server'], [['listen', '69.50.225.155:9000'],
['listen', '127.0.0.1'],
['server_name', '.example.com'],
['server_name', 'example.*']]]],
nparser.parsed[nparser.abs_path(
'sites-enabled/example.com')])
def test_abs_path(self):
nparser = parser.NginxParser(self.config_path, self.ssl_options)
self.assertEqual('/etc/nginx/*', nparser.abs_path('/etc/nginx/*'))
self.assertEqual(os.path.join(self.config_path, 'foo/bar/'),
nparser.abs_path('foo/bar/'))
def test_filedump(self):
nparser = parser.NginxParser(self.config_path, self.ssl_options)
nparser.filedump('test')
# pylint: disable=protected-access
parsed = nparser._parse_files(nparser.abs_path(
'sites-enabled/example.com.test'))
self.assertEqual(3, len(glob.glob(nparser.abs_path('*.test'))))
self.assertEqual(2, len(
glob.glob(nparser.abs_path('sites-enabled/*.test'))))
self.assertEqual([[['server'], [['listen', '69.50.225.155:9000'],
['listen', '127.0.0.1'],
['server_name', '.example.com'],
['server_name', 'example.*']]]],
parsed[0])
def test_get_vhosts(self):
nparser = parser.NginxParser(self.config_path, self.ssl_options)
vhosts = nparser.get_vhosts()
vhost1 = obj.VirtualHost(nparser.abs_path('nginx.conf'),
[obj.Addr('', '8080', False, False)],
False, True,
set(['localhost',
r'~^(www\.)?(example|bar)\.']),
[])
vhost2 = obj.VirtualHost(nparser.abs_path('nginx.conf'),
[obj.Addr('somename', '8080', False, False),
obj.Addr('', '8000', False, False)],
False, True,
set(['somename', 'another.alias', 'alias']),
[])
vhost3 = obj.VirtualHost(nparser.abs_path('sites-enabled/example.com'),
[obj.Addr('69.50.225.155', '9000',
False, False),
obj.Addr('127.0.0.1', '', False, False)],
False, True,
set(['.example.com', 'example.*']), [])
vhost4 = obj.VirtualHost(nparser.abs_path('sites-enabled/default'),
[obj.Addr('myhost', '', False, True)],
False, True, set(['www.example.org']), [])
vhost5 = obj.VirtualHost(nparser.abs_path('foo.conf'),
[obj.Addr('*', '80', True, True)],
True, True, set(['*.www.foo.com',
'*.www.example.com']), [])
self.assertEqual(5, len(vhosts))
example_com = [x for x in vhosts if 'example.com' in x.filep][0]
self.assertEqual(vhost3, example_com)
default = [x for x in vhosts if 'default' in x.filep][0]
self.assertEqual(vhost4, default)
fooconf = [x for x in vhosts if 'foo.conf' in x.filep][0]
self.assertEqual(vhost5, fooconf)
localhost = [x for x in vhosts if 'localhost' in x.names][0]
self.assertEquals(vhost1, localhost)
somename = [x for x in vhosts if 'somename' in x.names][0]
self.assertEquals(vhost2, somename)
def test_add_server_directives(self):
nparser = parser.NginxParser(self.config_path, self.ssl_options)
nparser.add_server_directives(nparser.abs_path('nginx.conf'),
set(['localhost',
r'~^(www\.)?(example|bar)\.']),
[['foo', 'bar'], ['ssl_certificate',
'/etc/ssl/cert.pem']])
ssl_re = re.compile(r'foo bar;\n\s+ssl_certificate /etc/ssl/cert.pem')
self.assertEqual(1, len(re.findall(ssl_re, nginxparser.dumps(
nparser.parsed[nparser.abs_path('nginx.conf')]))))
nparser.add_server_directives(nparser.abs_path('server.conf'),
set(['alias', 'another.alias',
'somename']),
[['foo', 'bar'], ['ssl_certificate',
'/etc/ssl/cert2.pem']])
self.assertEqual(nparser.parsed[nparser.abs_path('server.conf')],
[['server_name', 'somename alias another.alias'],
['foo', 'bar'],
['ssl_certificate', '/etc/ssl/cert2.pem']])
def test_replace_server_directives(self):
nparser = parser.NginxParser(self.config_path, self.ssl_options)
target = set(['.example.com', 'example.*'])
filep = nparser.abs_path('sites-enabled/example.com')
nparser.add_server_directives(
filep, target, [['server_name', 'foo bar']], True)
self.assertEqual(
nparser.parsed[filep],
[[['server'], [['listen', '69.50.225.155:9000'],
['listen', '127.0.0.1'],
['server_name', 'foo bar'],
['server_name', 'foo bar']]]])
self.assertRaises(LetsEncryptMisconfigurationError,
nparser.add_server_directives,
filep, set(['foo', 'bar']),
[['ssl_certificate', 'cert.pem']], True)
def test_get_best_match(self):
target_name = 'www.eff.org'
names = [set(['www.eff.org', 'irrelevant.long.name.eff.org', '*.org']),
set(['eff.org', 'ww2.eff.org', 'test.www.eff.org']),
set(['*.eff.org', '.www.eff.org']),
set(['.eff.org', '*.org']),
set(['www.eff.', 'www.eff.*', '*.www.eff.org']),
set(['example.com', r'~^(www\.)?(eff.+)', '*.eff.*']),
set(['*', r'~^(www\.)?(eff.+)']),
set(['www.*', r'~^(www\.)?(eff.+)', '.test.eff.org']),
set(['*.org', r'*.eff.org', 'www.eff.*']),
set(['*.www.eff.org', 'www.*']),
set(['*.org']),
set([]),
set(['example.com'])]
winners = [('exact', 'www.eff.org'),
(None, None),
('exact', '.www.eff.org'),
('wildcard_start', '.eff.org'),
('wildcard_end', 'www.eff.*'),
('regex', r'~^(www\.)?(eff.+)'),
('wildcard_start', '*'),
('wildcard_end', 'www.*'),
('wildcard_start', '*.eff.org'),
('wildcard_end', 'www.*'),
('wildcard_start', '*.org'),
(None, None),
(None, None)]
for i, winner in enumerate(winners):
self.assertEqual(winner,
parser.get_best_match(target_name, names[i]))
def test_get_all_certs_keys(self):
nparser = parser.NginxParser(self.config_path, self.ssl_options)
filep = nparser.abs_path('sites-enabled/example.com')
nparser.add_server_directives(filep,
set(['.example.com', 'example.*']),
[['ssl_certificate', 'foo.pem'],
['ssl_certificate_key', 'bar.key'],
['listen', '443 ssl']])
c_k = nparser.get_all_certs_keys()
self.assertEqual(set([('foo.pem', 'bar.key', filep)]), c_k)
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,25 @@
# a test nginx conf
user www-data;
http {
server {
listen *:80 default_server ssl;
server_name *.www.foo.com *.www.example.com;
root /home/ubuntu/sites/foo/;
location /status {
types {
image/jpeg jpg;
}
}
location ~ case_sensitive\.php$ {
index index.php;
root /var/root;
}
location ~* case_insensitive\.php$ {}
location = exact_match\.php$ {}
location ^~ ignore_regex\.php$ {}
}
}

View File

@@ -0,0 +1,119 @@
# standard default nginx config
user nobody;
worker_processes 1;
error_log logs/error.log;
error_log logs/error.log notice;
error_log logs/error.log info;
pid logs/nginx.pid;
events {
worker_connections 1024;
}
include foo.conf;
http {
include mime.types;
include sites-enabled/*;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log logs/access.log main;
sendfile on;
tcp_nopush on;
keepalive_timeout 0;
gzip on;
server {
listen 8080;
server_name localhost;
server_name ~^(www\.)?(example|bar)\.;
charset koi8-r;
access_log logs/host.access.log main;
location / {
root html;
index index.html index.htm;
}
error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
# proxy the PHP scripts to Nginx listening on 127.0.0.1:80
#
location ~ \.php$ {
proxy_pass http://127.0.0.1;
}
# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
location ~ \.php$ {
root html;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
}
# deny access to .htaccess files, if Nginx's document root
# concurs with nginx's one
#
location ~ /\.ht {
deny all;
}
}
# another virtual host using mix of IP-, name-, and port-based configuration
#
server {
listen 8000;
listen somename:8080;
include server.conf;
location / {
root html;
index index.html index.htm;
}
}
# HTTPS server
#
#server {
# listen 443 ssl;
# server_name localhost;
# ssl_certificate cert.pem;
# ssl_certificate_key cert.key;
# ssl_session_cache shared:SSL:1m;
# ssl_session_timeout 5m;
# ssl_ciphers HIGH:!aNULL:!MD5;
# ssl_prefer_server_ciphers on;
# location / {
# root html;
# index index.html index.htm;
# }
#}
#include conf.d/test.conf;
}

View File

@@ -0,0 +1,83 @@
user nobody;
worker_processes 1;
error_log logs/error.log;
error_log logs/error.log notice;
error_log logs/error.log info;
pid logs/nginx.pid;
events {
worker_connections 1024;
}
include foo.conf;
http {
include mime.types;
include sites-enabled/*;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log logs/access.log main;
sendfile on;
tcp_nopush on;
keepalive_timeout 0;
gzip on;
server {
listen 8080;
server_name localhost;
server_name ~^(www\.)?(example|bar)\.;
charset koi8-r;
access_log logs/host.access.log main;
location / {
root html;
index index.html index.htm;
}
error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
location ~ \.php$ {
proxy_pass http://127.0.0.1;
}
location ~ \.php$ {
root html;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
}
location ~ /\.ht {
deny all;
}
}
server {
listen 8000;
listen somename:8080;
include server.conf;
location / {
root html;
index index.html index.htm;
}
}
server {
listen 443 ssl;
server_name localhost;
ssl_certificate cert.pem;
ssl_certificate_key cert.key;
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 5m;
ssl_ciphers HIGH:!aNULL:!MD5;
location / {
root html;
index index.html index.htm;
}
}
}

View File

@@ -0,0 +1 @@
server_name somename alias another.alias;

View File

@@ -0,0 +1,9 @@
server {
listen myhost default_server;
server_name www.example.org;
location / {
root html;
index index.html index.htm;
}
}

View File

@@ -0,0 +1,6 @@
server {
listen 69.50.225.155:9000;
listen 127.0.0.1;
server_name .example.com;
server_name example.*;
}

View File

@@ -0,0 +1,25 @@
fastcgi_param QUERY_STRING $query_string;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGTH $content_length;
fastcgi_param SCRIPT_FILENAME $request_filename;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_param REQUEST_URI $request_uri;
fastcgi_param DOCUMENT_URI $document_uri;
fastcgi_param DOCUMENT_ROOT $document_root;
fastcgi_param SERVER_PROTOCOL $server_protocol;
fastcgi_param GATEWAY_INTERFACE CGI/1.1;
fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;
fastcgi_param REMOTE_ADDR $remote_addr;
fastcgi_param REMOTE_PORT $remote_port;
fastcgi_param SERVER_ADDR $server_addr;
fastcgi_param SERVER_PORT $server_port;
fastcgi_param SERVER_NAME $server_name;
fastcgi_param HTTPS $https if_not_empty;
# PHP only, required if PHP was built with --enable-force-cgi-redirect
fastcgi_param REDIRECT_STATUS 200;

View File

@@ -0,0 +1,108 @@
# This map is not a full koi8-r <> utf8 map: it does not contain
# box-drawing and some other characters. Besides this map contains
# several koi8-u and Byelorussian letters which are not in koi8-r.
# If you need a full and standard map, use contrib/unicode2nginx/koi-utf
# map instead.
charset_map koi8-r utf-8 {
80 E282AC; # euro
95 E280A2; # bullet
9A C2A0; # &nbsp;
9E C2B7; # &middot;
A3 D191; # small yo
A4 D194; # small Ukrainian ye
A6 D196; # small Ukrainian i
A7 D197; # small Ukrainian yi
AD D291; # small Ukrainian soft g
AE D19E; # small Byelorussian short u
B0 C2B0; # &deg;
B3 D081; # capital YO
B4 D084; # capital Ukrainian YE
B6 D086; # capital Ukrainian I
B7 D087; # capital Ukrainian YI
B9 E28496; # numero sign
BD D290; # capital Ukrainian soft G
BE D18E; # capital Byelorussian short U
BF C2A9; # (C)
C0 D18E; # small yu
C1 D0B0; # small a
C2 D0B1; # small b
C3 D186; # small ts
C4 D0B4; # small d
C5 D0B5; # small ye
C6 D184; # small f
C7 D0B3; # small g
C8 D185; # small kh
C9 D0B8; # small i
CA D0B9; # small j
CB D0BA; # small k
CC D0BB; # small l
CD D0BC; # small m
CE D0BD; # small n
CF D0BE; # small o
D0 D0BF; # small p
D1 D18F; # small ya
D2 D180; # small r
D3 D181; # small s
D4 D182; # small t
D5 D183; # small u
D6 D0B6; # small zh
D7 D0B2; # small v
D8 D18C; # small soft sign
D9 D18B; # small y
DA D0B7; # small z
DB D188; # small sh
DC D18D; # small e
DD D189; # small shch
DE D187; # small ch
DF D18A; # small hard sign
E0 D0AE; # capital YU
E1 D090; # capital A
E2 D091; # capital B
E3 D0A6; # capital TS
E4 D094; # capital D
E5 D095; # capital YE
E6 D0A4; # capital F
E7 D093; # capital G
E8 D0A5; # capital KH
E9 D098; # capital I
EA D099; # capital J
EB D09A; # capital K
EC D09B; # capital L
ED D09C; # capital M
EE D09D; # capital N
EF D09E; # capital O
F0 D09F; # capital P
F1 D0AF; # capital YA
F2 D0A0; # capital R
F3 D0A1; # capital S
F4 D0A2; # capital T
F5 D0A3; # capital U
F6 D096; # capital ZH
F7 D092; # capital V
F8 D0AC; # capital soft sign
F9 D0AB; # capital Y
FA D097; # capital Z
FB D0A8; # capital SH
FC D0AD; # capital E
FD D0A9; # capital SHCH
FE D0A7; # capital CH
FF D0AA; # capital hard sign
}

View File

@@ -0,0 +1,102 @@
charset_map koi8-r windows-1251 {
80 88; # euro
95 95; # bullet
9A A0; # &nbsp;
9E B7; # &middot;
A3 B8; # small yo
A4 BA; # small Ukrainian ye
A6 B3; # small Ukrainian i
A7 BF; # small Ukrainian yi
AD B4; # small Ukrainian soft g
AE A2; # small Byelorussian short u
B0 B0; # &deg;
B3 A8; # capital YO
B4 AA; # capital Ukrainian YE
B6 B2; # capital Ukrainian I
B7 AF; # capital Ukrainian YI
B9 B9; # numero sign
BD A5; # capital Ukrainian soft G
BE A1; # capital Byelorussian short U
BF A9; # (C)
C0 FE; # small yu
C1 E0; # small a
C2 E1; # small b
C3 F6; # small ts
C4 E4; # small d
C5 E5; # small ye
C6 F4; # small f
C7 E3; # small g
C8 F5; # small kh
C9 E8; # small i
CA E9; # small j
CB EA; # small k
CC EB; # small l
CD EC; # small m
CE ED; # small n
CF EE; # small o
D0 EF; # small p
D1 FF; # small ya
D2 F0; # small r
D3 F1; # small s
D4 F2; # small t
D5 F3; # small u
D6 E6; # small zh
D7 E2; # small v
D8 FC; # small soft sign
D9 FB; # small y
DA E7; # small z
DB F8; # small sh
DC FD; # small e
DD F9; # small shch
DE F7; # small ch
DF FA; # small hard sign
E0 DE; # capital YU
E1 C0; # capital A
E2 C1; # capital B
E3 D6; # capital TS
E4 C4; # capital D
E5 C5; # capital YE
E6 D4; # capital F
E7 C3; # capital G
E8 D5; # capital KH
E9 C8; # capital I
EA C9; # capital J
EB CA; # capital K
EC CB; # capital L
ED CC; # capital M
EE CD; # capital N
EF CE; # capital O
F0 CF; # capital P
F1 DF; # capital YA
F2 D0; # capital R
F3 D1; # capital S
F4 D2; # capital T
F5 D3; # capital U
F6 C6; # capital ZH
F7 C2; # capital V
F8 DC; # capital soft sign
F9 DB; # capital Y
FA C7; # capital Z
FB D8; # capital SH
FC DD; # capital E
FD D9; # capital SHCH
FE D7; # capital CH
FF DA; # capital hard sign
}

View File

@@ -0,0 +1,79 @@
types {
text/html html htm shtml;
text/css css;
text/xml xml rss;
image/gif gif;
image/jpeg jpeg jpg;
application/x-javascript js;
application/atom+xml atom;
text/mathml mml;
text/plain txt;
text/vnd.sun.j2me.app-descriptor jad;
text/vnd.wap.wml wml;
text/x-component htc;
image/png png;
image/tiff tif tiff;
image/vnd.wap.wbmp wbmp;
image/x-icon ico;
image/x-jng jng;
image/x-ms-bmp bmp;
image/svg+xml svg svgz;
application/java-archive jar war ear;
application/json json;
application/mac-binhex40 hqx;
application/msword doc;
application/pdf pdf;
application/postscript ps eps ai;
application/rtf rtf;
application/vnd.ms-excel xls;
application/vnd.ms-powerpoint ppt;
application/vnd.wap.wmlc wmlc;
application/vnd.google-earth.kml+xml kml;
application/vnd.google-earth.kmz kmz;
application/x-7z-compressed 7z;
application/x-cocoa cco;
application/x-java-archive-diff jardiff;
application/x-java-jnlp-file jnlp;
application/x-makeself run;
application/x-perl pl pm;
application/x-pilot prc pdb;
application/x-rar-compressed rar;
application/x-redhat-package-manager rpm;
application/x-sea sea;
application/x-shockwave-flash swf;
application/x-stuffit sit;
application/x-tcl tcl tk;
application/x-x509-ca-cert der pem crt;
application/x-xpinstall xpi;
application/xhtml+xml xhtml;
application/zip zip;
application/octet-stream bin exe dll;
application/octet-stream deb;
application/octet-stream dmg;
application/octet-stream eot;
application/octet-stream iso img;
application/octet-stream msi msp msm;
application/ogg ogx;
audio/midi mid midi kar;
audio/mpeg mpga mpega mp2 mp3 m4a;
audio/ogg oga ogg spx;
audio/x-realaudio ra;
audio/webm weba;
video/3gpp 3gpp 3gp;
video/mp4 mp4;
video/mpeg mpeg mpg mpe;
video/ogg ogv;
video/quicktime mov;
video/webm webm;
video/x-flv flv;
video/x-mng mng;
video/x-ms-asf asx asf;
video/x-ms-wmv wmv;
video/x-msvideo avi;
}

View File

@@ -0,0 +1,16 @@
[nx_extract]
username = naxsi_web
password = test
port = 8081
rules_path = /etc/nginx/naxsi_core.rules
[nx_intercept]
port = 8080
[sql]
dbtype = sqlite
username = root
password =
hostname = 127.0.0.1
dbname = naxsi_sig

View File

@@ -0,0 +1,13 @@
# Sample rules file for default vhost.
LearningMode;
SecRulesEnabled;
#SecRulesDisabled;
DeniedUrl "/RequestDenied";
## check rules
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$EVADE >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;

View File

@@ -0,0 +1,75 @@
##################################
## INTERNAL RULES IDS:1-10 ##
##################################
#weird_request : 1
#big_body : 2
#no_content_type : 3
#MainRule "str:yesone" "msg:foobar test pattern" "mz:ARGS" "s:$SQL:42" id:1999;
##################################
## SQL Injections IDs:1000-1099 ##
##################################
MainRule "rx:select|union|update|delete|insert|table|from|ascii|hex|unhex" "msg:sql keywords" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:4" id:1000;
MainRule "str:\"" "msg:double quote" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:4" id:1001;
MainRule "str:0x" "msg:0x, possible hex encoding" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:2" id:1002;
## Hardcore rules
MainRule "str:/*" "msg:mysql comment (/*)" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:8" id:1003;
MainRule "str:*/" "msg:mysql comment (*/)" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:8" id:1004;
MainRule "str:|" "msg:mysql keyword (|)" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:8" id:1005;
MainRule "rx:&&" "msg:mysql keyword (&&)" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:8" id:1006;
## end of hardcore rules
MainRule "str:--" "msg:mysql comment (--)" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:4" id:1007;
MainRule "str:;" "msg:; in stuff" "mz:BODY|URL|ARGS" "s:$SQL:4" id:1008;
MainRule "str:=" "msg:equal in var, probable sql/xss" "mz:ARGS|BODY" "s:$SQL:2" id:1009;
MainRule "str:(" "msg:parenthesis, probable sql/xss" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$SQL:4" id:1010;
MainRule "str:)" "msg:parenthesis, probable sql/xss" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$SQL:4" id:1011;
MainRule "str:'" "msg:simple quote" "mz:ARGS|BODY|URL|$HEADERS_VAR:Cookie" "s:$SQL:4" id:1013;
MainRule "str:\"" "msg:double quote" "mz:ARGS|BODY|URL|$HEADERS_VAR:Cookie" "s:$SQL:4" id:1014;
MainRule "str:," "msg:, in stuff" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:4" id:1015;
MainRule "str:#" "msg:mysql comment (#)" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:4" id:1016;
###############################
## OBVIOUS RFI IDs:1100-1199 ##
###############################
MainRule "str:http://" "msg:html comment tag" "mz:ARGS|BODY|$HEADERS_VAR:Cookie" "s:$RFI:8" id:1100;
MainRule "str:https://" "msg:html comment tag" "mz:ARGS|BODY|$HEADERS_VAR:Cookie" "s:$RFI:8" id:1101;
MainRule "str:ftp://" "msg:html comment tag" "mz:ARGS|BODY|$HEADERS_VAR:Cookie" "s:$RFI:8" id:1102;
MainRule "str:php://" "msg:html comment tag" "mz:ARGS|BODY|$HEADERS_VAR:Cookie" "s:$RFI:8" id:1103;
#######################################
## Directory traversal IDs:1200-1299 ##
#######################################
MainRule "str:.." "msg:html comment tag" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$TRAVERSAL:4" id:1200;
MainRule "str:/etc/passwd" "msg:html comment tag" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$TRAVERSAL:4" id:1202;
MainRule "str:c:\\" "msg:html comment tag" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$TRAVERSAL:4" id:1203;
MainRule "str:cmd.exe" "msg:html comment tag" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$TRAVERSAL:4" id:1204;
MainRule "str:\\" "msg:html comment tag" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$TRAVERSAL:4" id:1205;
#MainRule "str:/" "msg:slash in args" "mz:ARGS|BODY|$HEADERS_VAR:Cookie" "s:$TRAVERSAL:2" id:1206;
########################################
## Cross Site Scripting IDs:1300-1399 ##
########################################
MainRule "str:<" "msg:html open tag" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$XSS:8" id:1302;
MainRule "str:>" "msg:html close tag" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$XSS:8" id:1303;
MainRule "str:'" "msg:simple quote" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$XSS:8" id:1306;
MainRule "str:\"" "msg:double quote" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$XSS:8" id:1307;
MainRule "str:(" "msg:parenthesis" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$XSS:8" id:1308;
MainRule "str:)" "msg:parenthesis" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$XSS:8" id:1309;
MainRule "str:[" "msg:html close comment tag" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$XSS:4" id:1310;
MainRule "str:]" "msg:html close comment tag" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$XSS:4" id:1311;
MainRule "str:~" "msg:html close comment tag" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$XSS:4" id:1312;
MainRule "str:;" "msg:semi coma" "mz:ARGS|URL|BODY" "s:$XSS:8" id:1313;
MainRule "str:`" "msg:grave accent !" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$XSS:8" id:1314;
MainRule "rx:%[2|3]." "msg:double encoding !" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$XSS:8" id:1315;
####################################
## Evading tricks IDs: 1400-1500 ##
####################################
MainRule "str:&#" "msg: utf7/8 encoding" "mz:ARGS|BODY|URL|$HEADERS_VAR:Cookie" "s:$EVADE:4" id:1400;
MainRule "str:%U" "msg: M$ encoding" "mz:ARGS|BODY|URL|$HEADERS_VAR:Cookie" "s:$EVADE:4" id:1401;
MainRule negative "rx:multipart/form-data|application/x-www-form-urlencoded" "msg:Content is neither mulipart/x-www-form.." "mz:$HEADERS_VAR:Content-type" "s:$EVADE:4" id:1402;
#############################
## File uploads: 1500-1600 ##
#############################
MainRule "rx:.ph*|.asp*" "msg:asp/php file upload!" "mz:FILE_EXT" "s:$UPLOAD:8" id:1500;

View File

@@ -0,0 +1,95 @@
user www-data;
worker_processes 4;
pid /run/nginx.pid;
events {
worker_connections 768;
# multi_accept on;
}
http {
##
# Basic Settings
##
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
# server_tokens off;
# server_names_hash_bucket_size 64;
# server_name_in_redirect off;
include /etc/nginx/mime.types;
default_type application/octet-stream;
##
# Logging Settings
##
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
##
# Gzip Settings
##
gzip on;
gzip_disable "msie6";
# gzip_vary on;
# gzip_proxied any;
# gzip_comp_level 6;
# gzip_buffers 16 8k;
# gzip_http_version 1.1;
# gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;
##
# nginx-naxsi config
##
# Uncomment it if you installed nginx-naxsi
##
#include /etc/nginx/naxsi_core.rules;
##
# nginx-passenger config
##
# Uncomment it if you installed nginx-passenger
##
#passenger_root /usr;
#passenger_ruby /usr/bin/ruby;
##
# Virtual Host Configs
##
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
#mail {
# # See sample authentication script at:
# # http://wiki.nginx.org/ImapAuthenticateWithApachePhpScript
#
# # auth_http localhost/auth.php;
# # pop3_capabilities "TOP" "USER";
# # imap_capabilities "IMAP4rev1" "UIDPLUS";
#
# server {
# listen localhost:110;
# protocol pop3;
# proxy on;
# }
#
# server {
# listen localhost:143;
# protocol imap;
# proxy on;
# }
#}

View File

@@ -0,0 +1,4 @@
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;

View File

@@ -0,0 +1,14 @@
scgi_param REQUEST_METHOD $request_method;
scgi_param REQUEST_URI $request_uri;
scgi_param QUERY_STRING $query_string;
scgi_param CONTENT_TYPE $content_type;
scgi_param DOCUMENT_URI $document_uri;
scgi_param DOCUMENT_ROOT $document_root;
scgi_param SCGI 1;
scgi_param SERVER_PROTOCOL $server_protocol;
scgi_param REMOTE_ADDR $remote_addr;
scgi_param REMOTE_PORT $remote_port;
scgi_param SERVER_PORT $server_port;
scgi_param SERVER_NAME $server_name;

View File

@@ -0,0 +1,112 @@
# You may add here your
# server {
# ...
# }
# statements for each of your virtual hosts to this file
##
# You should look at the following URL's in order to grasp a solid understanding
# of Nginx configuration files in order to fully unleash the power of Nginx.
# http://wiki.nginx.org/Pitfalls
# http://wiki.nginx.org/QuickStart
# http://wiki.nginx.org/Configuration
#
# Generally, you will want to move this file somewhere, and start with a clean
# file but keep this around for reference. Or just disable in sites-enabled.
#
# Please see /usr/share/doc/nginx-doc/examples/ for more detailed examples.
##
server {
listen 80 default_server;
listen [::]:80 default_server ipv6only=on;
root /usr/share/nginx/html;
index index.html index.htm;
# Make site accessible from http://localhost/
server_name localhost;
location / {
# First attempt to serve request as file, then
# as directory, then fall back to displaying a 404.
try_files $uri $uri/ =404;
# Uncomment to enable naxsi on this location
# include /etc/nginx/naxsi.rules
}
# Only for nginx-naxsi used with nginx-naxsi-ui : process denied requests
#location /RequestDenied {
# proxy_pass http://127.0.0.1:8080;
#}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
#error_page 500 502 503 504 /50x.html;
#location = /50x.html {
# root /usr/share/nginx/html;
#}
# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
#location ~ \.php$ {
# fastcgi_split_path_info ^(.+\.php)(/.+)$;
# # NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini
#
# # With php5-cgi alone:
# fastcgi_pass 127.0.0.1:9000;
# # With php5-fpm:
# fastcgi_pass unix:/var/run/php5-fpm.sock;
# fastcgi_index index.php;
# include fastcgi_params;
#}
# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
}
# another virtual host using mix of IP-, name-, and port-based configuration
#
#server {
# listen 8000;
# listen somename:8080;
# server_name somename alias another.alias;
# root html;
# index index.html index.htm;
#
# location / {
# try_files $uri $uri/ =404;
# }
#}
# HTTPS server
#
#server {
# listen 443;
# server_name localhost;
#
# root html;
# index index.html index.htm;
#
# ssl on;
# ssl_certificate cert.pem;
# ssl_certificate_key cert.key;
#
# ssl_session_timeout 5m;
#
# ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2;
# ssl_ciphers "HIGH:!aNULL:!MD5 or HIGH:!aNULL:!MD5:!3DES";
# ssl_prefer_server_ciphers on;
#
# location / {
# try_files $uri $uri/ =404;
# }
#}

View File

@@ -0,0 +1 @@
/etc/nginx/sites-available/default

View File

@@ -0,0 +1,15 @@
uwsgi_param QUERY_STRING $query_string;
uwsgi_param REQUEST_METHOD $request_method;
uwsgi_param CONTENT_TYPE $content_type;
uwsgi_param CONTENT_LENGTH $content_length;
uwsgi_param REQUEST_URI $request_uri;
uwsgi_param PATH_INFO $document_uri;
uwsgi_param DOCUMENT_ROOT $document_root;
uwsgi_param SERVER_PROTOCOL $server_protocol;
uwsgi_param UWSGI_SCHEME $scheme;
uwsgi_param REMOTE_ADDR $remote_addr;
uwsgi_param REMOTE_PORT $remote_port;
uwsgi_param SERVER_PORT $server_port;
uwsgi_param SERVER_NAME $server_name;

View File

@@ -0,0 +1,125 @@
# This map is not a full windows-1251 <> utf8 map: it does not
# contain Serbian and Macedonian letters. If you need a full map,
# use contrib/unicode2nginx/win-utf map instead.
charset_map windows-1251 utf-8 {
82 E2809A; # single low-9 quotation mark
84 E2809E; # double low-9 quotation mark
85 E280A6; # ellipsis
86 E280A0; # dagger
87 E280A1; # double dagger
88 E282AC; # euro
89 E280B0; # per mille
91 E28098; # left single quotation mark
92 E28099; # right single quotation mark
93 E2809C; # left double quotation mark
94 E2809D; # right double quotation mark
95 E280A2; # bullet
96 E28093; # en dash
97 E28094; # em dash
99 E284A2; # trade mark sign
A0 C2A0; # &nbsp;
A1 D18E; # capital Byelorussian short U
A2 D19E; # small Byelorussian short u
A4 C2A4; # currency sign
A5 D290; # capital Ukrainian soft G
A6 C2A6; # borken bar
A7 C2A7; # section sign
A8 D081; # capital YO
A9 C2A9; # (C)
AA D084; # capital Ukrainian YE
AB C2AB; # left-pointing double angle quotation mark
AC C2AC; # not sign
AD C2AD; # soft hypen
AE C2AE; # (R)
AF D087; # capital Ukrainian YI
B0 C2B0; # &deg;
B1 C2B1; # plus-minus sign
B2 D086; # capital Ukrainian I
B3 D196; # small Ukrainian i
B4 D291; # small Ukrainian soft g
B5 C2B5; # micro sign
B6 C2B6; # pilcrow sign
B7 C2B7; # &middot;
B8 D191; # small yo
B9 E28496; # numero sign
BA D194; # small Ukrainian ye
BB C2BB; # right-pointing double angle quotation mark
BF D197; # small Ukrainian yi
C0 D090; # capital A
C1 D091; # capital B
C2 D092; # capital V
C3 D093; # capital G
C4 D094; # capital D
C5 D095; # capital YE
C6 D096; # capital ZH
C7 D097; # capital Z
C8 D098; # capital I
C9 D099; # capital J
CA D09A; # capital K
CB D09B; # capital L
CC D09C; # capital M
CD D09D; # capital N
CE D09E; # capital O
CF D09F; # capital P
D0 D0A0; # capital R
D1 D0A1; # capital S
D2 D0A2; # capital T
D3 D0A3; # capital U
D4 D0A4; # capital F
D5 D0A5; # capital KH
D6 D0A6; # capital TS
D7 D0A7; # capital CH
D8 D0A8; # capital SH
D9 D0A9; # capital SHCH
DA D0AA; # capital hard sign
DB D0AB; # capital Y
DC D0AC; # capital soft sign
DD D0AD; # capital E
DE D0AE; # capital YU
DF D0AF; # capital YA
E0 D0B0; # small a
E1 D0B1; # small b
E2 D0B2; # small v
E3 D0B3; # small g
E4 D0B4; # small d
E5 D0B5; # small ye
E6 D0B6; # small zh
E7 D0B7; # small z
E8 D0B8; # small i
E9 D0B9; # small j
EA D0BA; # small k
EB D0BB; # small l
EC D0BC; # small m
ED D0BD; # small n
EE D0BE; # small o
EF D0BF; # small p
F0 D180; # small r
F1 D181; # small s
F2 D182; # small t
F3 D183; # small u
F4 D184; # small f
F5 D185; # small kh
F6 D186; # small ts
F7 D187; # small ch
F8 D188; # small sh
F9 D189; # small shch
FA D18A; # small hard sign
FB D18B; # small y
FC D18C; # small soft sign
FD D18D; # small e
FE D18E; # small yu
FF D18F; # small ya
}

View File

@@ -0,0 +1,76 @@
"""Common utilities for letsencrypt.client.nginx."""
import os
import pkg_resources
import shutil
import tempfile
import unittest
import mock
from letsencrypt.client import constants
from letsencrypt.client.plugins.nginx import configurator
class NginxTest(unittest.TestCase): # pylint: disable=too-few-public-methods
def setUp(self):
super(NginxTest, self).setUp()
self.temp_dir, self.config_dir, self.work_dir = dir_setup(
"testdata")
self.ssl_options = setup_nginx_ssl_options(self.config_dir)
self.config_path = os.path.join(
self.temp_dir, "testdata")
self.rsa256_file = pkg_resources.resource_filename(
"letsencrypt.client.tests", "testdata/rsa256_key.pem")
self.rsa256_pem = pkg_resources.resource_string(
"letsencrypt.client.tests", "testdata/rsa256_key.pem")
def get_data_filename(filename):
"""Gets the filename of a test data file."""
return pkg_resources.resource_filename(
"letsencrypt.client.plugins.nginx.tests", "testdata/%s" % filename)
def dir_setup(test_dir="debian_nginx/two_vhost_80"):
"""Setup the directories necessary for the configurator."""
temp_dir = tempfile.mkdtemp("temp")
config_dir = tempfile.mkdtemp("config")
work_dir = tempfile.mkdtemp("work")
test_configs = pkg_resources.resource_filename(
"letsencrypt.client.plugins.nginx.tests", test_dir)
shutil.copytree(
test_configs, os.path.join(temp_dir, test_dir), symlinks=True)
return temp_dir, config_dir, work_dir
def setup_nginx_ssl_options(config_dir):
"""Move the ssl_options into position and return the path."""
option_path = os.path.join(config_dir, "options-ssl.conf")
shutil.copyfile(constants.NGINX_MOD_SSL_CONF, option_path)
return option_path
def get_nginx_configurator(
config_path, config_dir, work_dir, ssl_options, version=(1, 6, 2)):
"""Create an Nginx Configurator with the specified options."""
backups = os.path.join(work_dir, "backups")
config = configurator.NginxConfigurator(
mock.MagicMock(
nginx_server_root=config_path, nginx_mod_ssl_conf=ssl_options,
le_vhost_ext="-le-ssl.conf", backup_dir=backups,
config_dir=config_dir, work_dir=work_dir,
temp_checkpoint_dir=os.path.join(work_dir, "temp_checkpoints"),
in_progress_dir=os.path.join(backups, "IN_PROGRESS")),
version)
config.prepare()
return config

View File

@@ -124,6 +124,13 @@ def create_parser():
add("--apache-init-script", default="/etc/init.d/apache2",
help=config_help("apache_init_script"))
add("--nginx-server-root", default="/etc/nginx",
help=config_help("nginx_server_root"))
add("--nginx-mod-ssl-conf",
default="/etc/letsencrypt/options-ssl-nginx.conf",
help=config_help("nginx_mod_ssl_conf"))
add("--nginx-ctl", default="nginx", help=config_help("nginx_ctl"))
return parser

View File

@@ -11,6 +11,7 @@ from setuptools import setup
if os.path.abspath(__file__).split(os.path.sep)[1] == 'vagrant':
del os.link
def read_file(filename, encoding='utf8'):
"""Read unicode from given file."""
with codecs.open(filename, encoding=encoding) as fd:
@@ -37,6 +38,7 @@ install_requires = [
'pyasn1', # urllib3 InsecurePlatformWarning (#304)
'pycrypto',
'PyOpenSSL',
'pyparsing>=1.5.5', # Python3 support; perhaps unnecessary?
'pyrfc3339',
'python-augeas',
'python2-pythondialog>=3.2.2rc1', # Debian squeeze support, cf. #280
@@ -104,6 +106,8 @@ setup(
'letsencrypt.client.plugins',
'letsencrypt.client.plugins.apache',
'letsencrypt.client.plugins.apache.tests',
'letsencrypt.client.plugins.nginx',
'letsencrypt.client.plugins.nginx.tests',
'letsencrypt.client.plugins.standalone',
'letsencrypt.client.plugins.standalone.tests',
'letsencrypt.client.tests',
@@ -129,6 +133,8 @@ setup(
'letsencrypt.authenticators': [
'apache = letsencrypt.client.plugins.apache.configurator'
':ApacheConfigurator',
'nginx = letsencrypt.client.plugins.nginx.configurator'
':NginxConfigurator',
'standalone = letsencrypt.client.plugins.standalone.authenticator'
':StandaloneAuthenticator',
],