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:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,5 +1,6 @@
|
||||
*.pyc
|
||||
*.egg-info
|
||||
.eggs/
|
||||
build/
|
||||
dist/
|
||||
venv/
|
||||
|
||||
@@ -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"
|
||||
|
||||
30
LICENSE.txt
30
LICENSE.txt
@@ -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
2
Vagrantfile
vendored
@@ -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
35
bootstrap/_deb_common.sh
Executable 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
|
||||
@@ -1 +1 @@
|
||||
ubuntu.sh
|
||||
_deb_common.sh
|
||||
@@ -1,2 +1,2 @@
|
||||
#!/bin/sh
|
||||
sudo brew install augeas swig
|
||||
brew install augeas swig
|
||||
|
||||
@@ -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
1
bootstrap/ubuntu.sh
Symbolic link
@@ -0,0 +1 @@
|
||||
_deb_common.sh
|
||||
35
docs/api/client/plugins/nginx.rst
Normal file
35
docs/api/client/plugins/nginx.rst
Normal 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:
|
||||
@@ -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
|
||||
|
||||
@@ -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."""
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
1
letsencrypt/client/plugins/nginx/__init__.py
Normal file
1
letsencrypt/client/plugins/nginx/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Let's Encrypt client.plugins.nginx."""
|
||||
561
letsencrypt/client/plugins/nginx/configurator.py
Normal file
561
letsencrypt/client/plugins/nginx/configurator.py
Normal 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)
|
||||
63
letsencrypt/client/plugins/nginx/dvsni.py
Normal file
63
letsencrypt/client/plugins/nginx/dvsni.py
Normal 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
|
||||
130
letsencrypt/client/plugins/nginx/nginxparser.py
Normal file
130
letsencrypt/client/plugins/nginx/nginxparser.py
Normal 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))
|
||||
125
letsencrypt/client/plugins/nginx/obj.py
Normal file
125
letsencrypt/client/plugins/nginx/obj.py
Normal 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
|
||||
8
letsencrypt/client/plugins/nginx/options-ssl.conf
Normal file
8
letsencrypt/client/plugins/nginx/options-ssl.conf
Normal 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";
|
||||
484
letsencrypt/client/plugins/nginx/parser.py
Normal file
484
letsencrypt/client/plugins/nginx/parser.py
Normal 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])
|
||||
1
letsencrypt/client/plugins/nginx/tests/__init__.py
Normal file
1
letsencrypt/client/plugins/nginx/tests/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Let's Encrypt Nginx Tests"""
|
||||
264
letsencrypt/client/plugins/nginx/tests/configurator_test.py
Normal file
264
letsencrypt/client/plugins/nginx/tests/configurator_test.py
Normal 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()
|
||||
85
letsencrypt/client/plugins/nginx/tests/dvsni_test.py
Normal file
85
letsencrypt/client/plugins/nginx/tests/dvsni_test.py
Normal 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()
|
||||
108
letsencrypt/client/plugins/nginx/tests/nginxparser_test.py
Normal file
108
letsencrypt/client/plugins/nginx/tests/nginxparser_test.py
Normal 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()
|
||||
105
letsencrypt/client/plugins/nginx/tests/obj_test.py
Normal file
105
letsencrypt/client/plugins/nginx/tests/obj_test.py
Normal 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()
|
||||
206
letsencrypt/client/plugins/nginx/tests/parser_test.py
Normal file
206
letsencrypt/client/plugins/nginx/tests/parser_test.py
Normal 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()
|
||||
25
letsencrypt/client/plugins/nginx/tests/testdata/foo.conf
vendored
Normal file
25
letsencrypt/client/plugins/nginx/tests/testdata/foo.conf
vendored
Normal 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$ {}
|
||||
|
||||
}
|
||||
}
|
||||
0
letsencrypt/client/plugins/nginx/tests/testdata/mime.types
vendored
Normal file
0
letsencrypt/client/plugins/nginx/tests/testdata/mime.types
vendored
Normal file
119
letsencrypt/client/plugins/nginx/tests/testdata/nginx.conf
vendored
Normal file
119
letsencrypt/client/plugins/nginx/tests/testdata/nginx.conf
vendored
Normal 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;
|
||||
}
|
||||
83
letsencrypt/client/plugins/nginx/tests/testdata/nginx.new.conf
vendored
Normal file
83
letsencrypt/client/plugins/nginx/tests/testdata/nginx.new.conf
vendored
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
1
letsencrypt/client/plugins/nginx/tests/testdata/server.conf
vendored
Normal file
1
letsencrypt/client/plugins/nginx/tests/testdata/server.conf
vendored
Normal file
@@ -0,0 +1 @@
|
||||
server_name somename alias another.alias;
|
||||
9
letsencrypt/client/plugins/nginx/tests/testdata/sites-enabled/default
vendored
Normal file
9
letsencrypt/client/plugins/nginx/tests/testdata/sites-enabled/default
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
server {
|
||||
listen myhost default_server;
|
||||
server_name www.example.org;
|
||||
|
||||
location / {
|
||||
root html;
|
||||
index index.html index.htm;
|
||||
}
|
||||
}
|
||||
6
letsencrypt/client/plugins/nginx/tests/testdata/sites-enabled/example.com
vendored
Normal file
6
letsencrypt/client/plugins/nginx/tests/testdata/sites-enabled/example.com
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
server {
|
||||
listen 69.50.225.155:9000;
|
||||
listen 127.0.0.1;
|
||||
server_name .example.com;
|
||||
server_name example.*;
|
||||
}
|
||||
@@ -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;
|
||||
108
letsencrypt/client/plugins/nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/koi-utf
vendored
Normal file
108
letsencrypt/client/plugins/nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/koi-utf
vendored
Normal 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; #
|
||||
|
||||
9E C2B7; # ·
|
||||
|
||||
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; # °
|
||||
|
||||
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
|
||||
}
|
||||
102
letsencrypt/client/plugins/nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/koi-win
vendored
Normal file
102
letsencrypt/client/plugins/nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/koi-win
vendored
Normal file
@@ -0,0 +1,102 @@
|
||||
charset_map koi8-r windows-1251 {
|
||||
|
||||
80 88; # euro
|
||||
|
||||
95 95; # bullet
|
||||
|
||||
9A A0; #
|
||||
|
||||
9E B7; # ·
|
||||
|
||||
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; # °
|
||||
|
||||
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
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
# }
|
||||
#}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
# }
|
||||
#}
|
||||
@@ -0,0 +1 @@
|
||||
/etc/nginx/sites-available/default
|
||||
@@ -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;
|
||||
125
letsencrypt/client/plugins/nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/win-utf
vendored
Normal file
125
letsencrypt/client/plugins/nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/win-utf
vendored
Normal 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; #
|
||||
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; # °
|
||||
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; # ·
|
||||
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
|
||||
}
|
||||
76
letsencrypt/client/plugins/nginx/tests/util.py
Normal file
76
letsencrypt/client/plugins/nginx/tests/util.py
Normal 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
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
6
setup.py
6
setup.py
@@ -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',
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user