mirror of
https://github.com/certbot/certbot.git
synced 2026-08-02 00:22:28 +02:00
s/letsencrypt/certbot letsencrypt-nginx tests
This commit is contained in:
@@ -0,0 +1,216 @@
|
||||
Copyright 2015 Electronic Frontier Foundation and others
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
Incorporating code from nginxparser
|
||||
Copyright 2014 Fatih Erikli
|
||||
Licensed MIT
|
||||
|
||||
|
||||
Text of Apache License
|
||||
======================
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
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.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
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.
|
||||
@@ -0,0 +1,5 @@
|
||||
include LICENSE.txt
|
||||
include README.rst
|
||||
recursive-include docs *
|
||||
recursive-include certbot_nginx/tests/testdata *
|
||||
include certbot_nginx/options-ssl-nginx.conf
|
||||
@@ -0,0 +1 @@
|
||||
Nginx plugin for Let's Encrypt client
|
||||
@@ -0,0 +1 @@
|
||||
"""Let's Encrypt nginx plugin."""
|
||||
@@ -0,0 +1,699 @@
|
||||
"""Nginx Configuration"""
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import socket
|
||||
import subprocess
|
||||
import time
|
||||
|
||||
import OpenSSL
|
||||
import zope.interface
|
||||
|
||||
from acme import challenges
|
||||
from acme import crypto_util as acme_crypto_util
|
||||
|
||||
from certbot import constants as core_constants
|
||||
from certbot import crypto_util
|
||||
from certbot import errors
|
||||
from certbot import interfaces
|
||||
from certbot import le_util
|
||||
from certbot import reverter
|
||||
|
||||
from certbot.plugins import common
|
||||
|
||||
from certbot_nginx import constants
|
||||
from certbot_nginx import tls_sni_01
|
||||
from certbot_nginx import obj
|
||||
from certbot_nginx import parser
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@zope.interface.implementer(interfaces.IAuthenticator, interfaces.IInstaller)
|
||||
@zope.interface.provider(interfaces.IPluginFactory)
|
||||
class NginxConfigurator(common.Plugin):
|
||||
# pylint: disable=too-many-instance-attributes,too-many-public-methods
|
||||
"""Nginx configurator.
|
||||
|
||||
.. 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:`~certbot.interfaces.IConfig`
|
||||
|
||||
:ivar parser: Handles low level parsing
|
||||
:type parser: :class:`~certbot_nginx.parser`
|
||||
|
||||
:ivar str save_notes: Human-readable config change notes
|
||||
|
||||
:ivar reverter: saves and reverts checkpoints
|
||||
:type reverter: :class:`certbot.reverter.Reverter`
|
||||
|
||||
:ivar tup version: version of Nginx
|
||||
|
||||
"""
|
||||
|
||||
description = "Nginx Web Server - currently doesn't work"
|
||||
|
||||
@classmethod
|
||||
def add_parser_arguments(cls, add):
|
||||
add("server-root", default=constants.CLI_DEFAULTS["server_root"],
|
||||
help="Nginx server root directory.")
|
||||
add("ctl", default=constants.CLI_DEFAULTS["ctl"], help="Path to the "
|
||||
"'nginx' binary, used for 'configtest' and retrieving nginx "
|
||||
"version number.")
|
||||
|
||||
@property
|
||||
def nginx_conf(self):
|
||||
"""Nginx config file path."""
|
||||
return os.path.join(self.conf("server_root"), "nginx.conf")
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Initialize an Nginx Configurator.
|
||||
|
||||
:param tup version: version of Nginx as a tuple (1, 4, 7)
|
||||
(used mostly for unittesting)
|
||||
|
||||
"""
|
||||
version = kwargs.pop("version", None)
|
||||
super(NginxConfigurator, self).__init__(*args, **kwargs)
|
||||
|
||||
# Verify that all directories and files exist with proper permissions
|
||||
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 = {"redirect": self._enable_redirect}
|
||||
|
||||
# Set up reverter
|
||||
self.reverter = reverter.Reverter(self.config)
|
||||
self.reverter.recovery_routine()
|
||||
|
||||
@property
|
||||
def mod_ssl_conf(self):
|
||||
"""Full absolute path to SSL configuration file."""
|
||||
return os.path.join(self.config.config_dir, constants.MOD_SSL_CONF_DEST)
|
||||
|
||||
# This is called in determine_authenticator and determine_installer
|
||||
def prepare(self):
|
||||
"""Prepare the authenticator/installer.
|
||||
|
||||
:raises .errors.NoInstallationError: If Nginx ctl cannot be found
|
||||
:raises .errors.MisconfigurationError: If Nginx is misconfigured
|
||||
"""
|
||||
# Verify Nginx is installed
|
||||
if not le_util.exe_exists(self.conf('ctl')):
|
||||
raise errors.NoInstallationError
|
||||
|
||||
# Make sure configuration is valid
|
||||
self.config_test()
|
||||
|
||||
self.parser = parser.NginxParser(
|
||||
self.conf('server-root'), self.mod_ssl_conf)
|
||||
|
||||
# Set Version
|
||||
if self.version is None:
|
||||
self.version = self.get_version()
|
||||
|
||||
temp_install(self.mod_ssl_conf)
|
||||
|
||||
# Entry point in main.py for installing cert
|
||||
def deploy_cert(self, domain, cert_path, key_path,
|
||||
chain_path=None, fullchain_path=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.
|
||||
It expects the cert file to have the concatenated chain.
|
||||
However, we use the chain file as input to the
|
||||
ssl_trusted_certificate directive, used for verify OCSP responses.
|
||||
|
||||
.. note:: This doesn't save the config files!
|
||||
|
||||
:raises errors.PluginError: When unable to deploy certificate due to
|
||||
a lack of directives or configuration
|
||||
|
||||
"""
|
||||
if not fullchain_path:
|
||||
raise errors.PluginError(
|
||||
"The nginx plugin currently requires --fullchain-path to "
|
||||
"install a cert.")
|
||||
|
||||
vhost = self.choose_vhost(domain)
|
||||
cert_directives = [['ssl_certificate', fullchain_path],
|
||||
['ssl_certificate_key', key_path]]
|
||||
|
||||
# OCSP stapling was introduced in Nginx 1.3.7. If we have that version
|
||||
# or greater, add config settings for it.
|
||||
stapling_directives = []
|
||||
if self.version >= (1, 3, 7):
|
||||
stapling_directives = [
|
||||
['ssl_trusted_certificate', chain_path],
|
||||
['ssl_stapling', 'on'],
|
||||
['ssl_stapling_verify', 'on']]
|
||||
|
||||
if len(stapling_directives) != 0 and not chain_path:
|
||||
raise errors.PluginError(
|
||||
"--chain-path is required to enable "
|
||||
"Online Certificate Status Protocol (OCSP) stapling "
|
||||
"on nginx >= 1.3.7.")
|
||||
|
||||
try:
|
||||
self.parser.add_server_directives(vhost.filep, vhost.names,
|
||||
cert_directives, replace=True)
|
||||
self.parser.add_server_directives(vhost.filep, vhost.names,
|
||||
stapling_directives, replace=False)
|
||||
logger.info("Deployed Certificate to VirtualHost %s for %s",
|
||||
vhost.filep, vhost.names)
|
||||
except errors.MisconfigurationError as error:
|
||||
logger.debug(error)
|
||||
logger.warn(
|
||||
"Cannot find a cert or key directive in %s for %s. "
|
||||
"VirtualHost was not modified.", vhost.filep, vhost.names)
|
||||
# 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" % fullchain_path
|
||||
self.save_notes += "\tssl_certificate_key %s\n" % key_path
|
||||
if len(stapling_directives) > 0:
|
||||
self.save_notes += "\tssl_trusted_certificate %s\n" % chain_path
|
||||
self.save_notes += "\tssl_stapling on\n"
|
||||
self.save_notes += "\tssl_stapling_verify on\n"
|
||||
|
||||
|
||||
|
||||
#######################
|
||||
# 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:`~certbot_nginx.obj.VirtualHost`
|
||||
|
||||
"""
|
||||
vhost = None
|
||||
|
||||
matches = self._get_ranked_matches(target_name)
|
||||
if not matches:
|
||||
# No matches. Create a new vhost with this name in nginx.conf.
|
||||
filep = self.parser.loc["root"]
|
||||
new_block = [['server'], [['server_name', target_name]]]
|
||||
self.parser.add_http_directives(filep, new_block)
|
||||
vhost = obj.VirtualHost(filep, set([]), False, True,
|
||||
set([target_name]), list(new_block[1]))
|
||||
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)
|
||||
|
||||
return vhost
|
||||
|
||||
def _get_ranked_matches(self, target_name):
|
||||
"""Returns a ranked list of vhosts that match target_name.
|
||||
The ranking gives preference to SSL vhosts.
|
||||
|
||||
: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()
|
||||
|
||||
for vhost in self.parser.get_vhosts():
|
||||
all_names.update(vhost.names)
|
||||
|
||||
for addr in vhost.addrs:
|
||||
host = addr.get_addr()
|
||||
if common.hostname_regex.match(host):
|
||||
# If it's a hostname, add it to the names.
|
||||
all_names.add(host)
|
||||
elif not common.private_ips_regex.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 _get_snakeoil_paths(self):
|
||||
# TODO: generate only once
|
||||
tmp_dir = os.path.join(self.config.work_dir, "snakeoil")
|
||||
le_key = crypto_util.init_save_key(
|
||||
key_size=1024, key_dir=tmp_dir, keyname="key.pem")
|
||||
key = OpenSSL.crypto.load_privatekey(
|
||||
OpenSSL.crypto.FILETYPE_PEM, le_key.pem)
|
||||
cert = acme_crypto_util.gen_ss_cert(key, domains=[socket.gethostname()])
|
||||
cert_pem = OpenSSL.crypto.dump_certificate(
|
||||
OpenSSL.crypto.FILETYPE_PEM, cert)
|
||||
cert_file, cert_path = le_util.unique_file(os.path.join(tmp_dir, "cert.pem"))
|
||||
with cert_file:
|
||||
cert_file.write(cert_pem)
|
||||
return cert_path, le_key.file
|
||||
|
||||
def _make_server_ssl(self, vhost):
|
||||
"""Make a server SSL.
|
||||
|
||||
Make a server SSL based on server_name and filename by adding a
|
||||
``listen IConfig.tls_sni_01_port ssl`` directive to the server block.
|
||||
|
||||
.. todo:: Maybe this should create a new block instead of modifying
|
||||
the existing one?
|
||||
|
||||
:param vhost: The vhost to add SSL to.
|
||||
:type vhost: :class:`~certbot_nginx.obj.VirtualHost`
|
||||
|
||||
"""
|
||||
snakeoil_cert, snakeoil_key = self._get_snakeoil_paths()
|
||||
ssl_block = [['listen', '{0} ssl'.format(self.config.tls_sni_01_port)],
|
||||
['ssl_certificate', snakeoil_cert],
|
||||
['ssl_certificate_key', snakeoil_key],
|
||||
['include', self.parser.loc["ssl_options"]]]
|
||||
self.parser.add_server_directives(
|
||||
vhost.filep, vhost.names, ssl_block, replace=False)
|
||||
vhost.ssl = True
|
||||
vhost.raw.extend(ssl_block)
|
||||
vhost.addrs.add(obj.Addr(
|
||||
'', str(self.config.tls_sni_01_port), True, False))
|
||||
|
||||
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 ['redirect']
|
||||
|
||||
def enhance(self, domain, enhancement, options=None):
|
||||
"""Enhance configuration.
|
||||
|
||||
:param str domain: domain to enhance
|
||||
:param str enhancement: enhancement type defined in
|
||||
:const:`~certbot.constants.ENHANCEMENTS`
|
||||
:param options: options for the enhancement
|
||||
See :const:`~certbot.constants.ENHANCEMENTS`
|
||||
documentation for appropriate parameter.
|
||||
|
||||
"""
|
||||
try:
|
||||
return self._enhance_func[enhancement](
|
||||
self.choose_vhost(domain), options)
|
||||
except (KeyError, ValueError):
|
||||
raise errors.PluginError(
|
||||
"Unsupported enhancement: {0}".format(enhancement))
|
||||
except errors.PluginError:
|
||||
logger.warn("Failed %s for %s", enhancement, domain)
|
||||
|
||||
def _enable_redirect(self, vhost, unused_options):
|
||||
"""Redirect all equivalent HTTP traffic to ssl_vhost.
|
||||
|
||||
Add rewrite directive to non https traffic
|
||||
|
||||
.. note:: This function saves the configuration
|
||||
|
||||
:param vhost: Destination of traffic, an ssl enabled vhost
|
||||
:type vhost: :class:`~certbot_nginx.obj.VirtualHost`
|
||||
|
||||
:param unused_options: Not currently used
|
||||
:type unused_options: Not Available
|
||||
"""
|
||||
redirect_block = [[
|
||||
['if', '($scheme != "https")'],
|
||||
[['return', '301 https://$host$request_uri']]
|
||||
]]
|
||||
self.parser.add_server_directives(
|
||||
vhost.filep, vhost.names, redirect_block, replace=False)
|
||||
logger.info("Redirecting all traffic to ssl in %s", vhost.filep)
|
||||
|
||||
######################################
|
||||
# Nginx server management (IInstaller)
|
||||
######################################
|
||||
def restart(self):
|
||||
"""Restarts nginx server.
|
||||
|
||||
:raises .errors.MisconfigurationError: If either the reload fails.
|
||||
|
||||
"""
|
||||
nginx_restart(self.conf('ctl'), self.nginx_conf)
|
||||
|
||||
def config_test(self): # pylint: disable=no-self-use
|
||||
"""Check the configuration of Nginx for errors.
|
||||
|
||||
:raises .errors.MisconfigurationError: If config_test fails
|
||||
|
||||
"""
|
||||
try:
|
||||
le_util.run_script([self.conf('ctl'), "-c", self.nginx_conf, "-t"])
|
||||
except errors.SubprocessError as err:
|
||||
raise errors.MisconfigurationError(str(err))
|
||||
|
||||
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, core_constants.CONFIG_DIRS_MODE, uid)
|
||||
le_util.make_or_verify_dir(
|
||||
self.config.backup_dir, core_constants.CONFIG_DIRS_MODE, uid)
|
||||
le_util.make_or_verify_dir(
|
||||
self.config.config_dir, core_constants.CONFIG_DIRS_MODE, 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 .PluginError:
|
||||
Unable to find Nginx version or version is unsupported
|
||||
|
||||
"""
|
||||
try:
|
||||
proc = subprocess.Popen(
|
||||
[self.conf('ctl'), "-c", self.nginx_conf, "-V"],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
text = proc.communicate()[1] # nginx prints output to stderr
|
||||
except (OSError, ValueError) as error:
|
||||
logging.debug(error, exc_info=True)
|
||||
raise errors.PluginError(
|
||||
"Unable to run %s -V" % self.conf('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.PluginError("Unable to find Nginx version")
|
||||
if not ssl_matches:
|
||||
raise errors.PluginError(
|
||||
"Nginx build is missing SSL module (--with-http_ssl_module).")
|
||||
if not sni_matches:
|
||||
raise errors.PluginError("Nginx build doesn't support SNI")
|
||||
|
||||
nginx_version = tuple([int(i) for i in version_matches[0].split(".")])
|
||||
|
||||
# nginx < 0.8.48 uses machine hostname as default server_name instead of
|
||||
# the empty string
|
||||
if nginx_version < (0, 8, 48):
|
||||
raise errors.NotSupportedError("Nginx version must be 0.8.48+")
|
||||
|
||||
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)
|
||||
|
||||
:raises .errors.PluginError: If there was an error in
|
||||
an attempt to save the configuration, or an error creating a
|
||||
checkpoint
|
||||
|
||||
"""
|
||||
save_files = set(self.parser.parsed.keys())
|
||||
|
||||
try:
|
||||
# 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)
|
||||
except errors.ReverterError as err:
|
||||
raise errors.PluginError(str(err))
|
||||
|
||||
self.save_notes = ""
|
||||
|
||||
# Change 'ext' to something else to not override existing conf files
|
||||
self.parser.filedump(ext='')
|
||||
if title and not temporary:
|
||||
try:
|
||||
self.reverter.finalize_checkpoint(title)
|
||||
except errors.ReverterError as err:
|
||||
raise errors.PluginError(str(err))
|
||||
|
||||
return True
|
||||
|
||||
def recovery_routine(self):
|
||||
"""Revert all previously modified files.
|
||||
|
||||
Reverts all modified files that have not been saved as a checkpoint
|
||||
|
||||
:raises .errors.PluginError: If unable to recover the configuration
|
||||
|
||||
"""
|
||||
try:
|
||||
self.reverter.recovery_routine()
|
||||
except errors.ReverterError as err:
|
||||
raise errors.PluginError(str(err))
|
||||
self.parser.load()
|
||||
|
||||
def revert_challenge_config(self):
|
||||
"""Used to cleanup challenge configurations.
|
||||
|
||||
:raises .errors.PluginError: If unable to revert the challenge config.
|
||||
|
||||
"""
|
||||
try:
|
||||
self.reverter.revert_temporary_config()
|
||||
except errors.ReverterError as err:
|
||||
raise errors.PluginError(str(err))
|
||||
self.parser.load()
|
||||
|
||||
def rollback_checkpoints(self, rollback=1):
|
||||
"""Rollback saved checkpoints.
|
||||
|
||||
:param int rollback: Number of checkpoints to revert
|
||||
|
||||
:raises .errors.PluginError: If there is a problem with the input or
|
||||
the function is unable to correctly revert the configuration
|
||||
|
||||
"""
|
||||
try:
|
||||
self.reverter.rollback_checkpoints(rollback)
|
||||
except errors.ReverterError as err:
|
||||
raise errors.PluginError(str(err))
|
||||
self.parser.load()
|
||||
|
||||
def view_config_changes(self):
|
||||
"""Show all of the configuration changes that have taken place.
|
||||
|
||||
:raises .errors.PluginError: If there is a problem while processing
|
||||
the checkpoints directories.
|
||||
|
||||
"""
|
||||
try:
|
||||
self.reverter.view_config_changes()
|
||||
except errors.ReverterError as err:
|
||||
raise errors.PluginError(str(err))
|
||||
|
||||
###########################################################################
|
||||
# Challenges Section for IAuthenticator
|
||||
###########################################################################
|
||||
def get_chall_pref(self, unused_domain): # pylint: disable=no-self-use
|
||||
"""Return list of challenge preferences."""
|
||||
return [challenges.TLSSNI01]
|
||||
|
||||
# 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)
|
||||
chall_doer = tls_sni_01.NginxTlsSni01(self)
|
||||
|
||||
for i, achall in enumerate(achalls):
|
||||
# Currently also have chall_doer hold associated index of the
|
||||
# challenge. This helps to put all of the responses back together
|
||||
# when they are all complete.
|
||||
chall_doer.add_chall(achall, i)
|
||||
|
||||
sni_response = chall_doer.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[chall_doer.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, nginx_conf="/etc/nginx.conf"):
|
||||
"""Restarts the Nginx Server.
|
||||
|
||||
.. todo:: Nginx restart is fatal if the configuration references
|
||||
non-existent SSL cert/key files. Remove references to /etc/letsencrypt
|
||||
before restart.
|
||||
|
||||
:param str nginx_ctl: Path to the Nginx binary.
|
||||
|
||||
"""
|
||||
try:
|
||||
proc = subprocess.Popen([nginx_ctl, "-c", nginx_conf, "-s", "reload"],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
stdout, stderr = proc.communicate()
|
||||
|
||||
if proc.returncode != 0:
|
||||
# Maybe Nginx isn't running
|
||||
nginx_proc = subprocess.Popen([nginx_ctl, "-c", nginx_conf],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
stdout, stderr = nginx_proc.communicate()
|
||||
|
||||
if nginx_proc.returncode != 0:
|
||||
# Enter recovery routine...
|
||||
raise errors.MisconfigurationError(
|
||||
"nginx restart failed:\n%s\n%s" % (stdout, stderr))
|
||||
|
||||
except (OSError, ValueError):
|
||||
raise errors.MisconfigurationError("nginx restart failed")
|
||||
# Nginx can take a moment to recognize a newly added TLS SNI servername, so sleep
|
||||
# for a second. TODO: Check for expected servername and loop until it
|
||||
# appears or return an error if looping too long.
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
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.MOD_SSL_CONF_SRC, options_ssl)
|
||||
@@ -0,0 +1,18 @@
|
||||
"""nginx plugin constants."""
|
||||
import pkg_resources
|
||||
|
||||
|
||||
CLI_DEFAULTS = dict(
|
||||
server_root="/etc/nginx",
|
||||
ctl="nginx",
|
||||
)
|
||||
"""CLI defaults."""
|
||||
|
||||
|
||||
MOD_SSL_CONF_DEST = "options-ssl-nginx.conf"
|
||||
"""Name of the mod_ssl config file as saved in `IConfig.config_dir`."""
|
||||
|
||||
MOD_SSL_CONF_SRC = pkg_resources.resource_filename(
|
||||
"certbot_nginx", "options-ssl-nginx.conf")
|
||||
"""Path to the nginx mod_ssl config file found in the Let's Encrypt
|
||||
distribution."""
|
||||
@@ -0,0 +1,137 @@
|
||||
"""Very low-level nginx config parser based on pyparsing."""
|
||||
import string
|
||||
|
||||
from pyparsing import (
|
||||
Literal, White, Word, alphanums, CharsNotIn, Forward, Group,
|
||||
Optional, OneOrMore, Regex, ZeroOrMore)
|
||||
from pyparsing import stringEnd
|
||||
from pyparsing import restOfLine
|
||||
|
||||
|
||||
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 + "_/")
|
||||
# Matches anything that is not a special character AND any chars in single
|
||||
# or double quotes
|
||||
value = Regex(r"((\".*\")?(\'.*\')?[^\{\};,]?)+")
|
||||
location = CharsNotIn("{};," + string.whitespace)
|
||||
# modifier for location uri [ = | ~ | ~* | ^~ ]
|
||||
modifier = Literal("=") | Literal("~*") | Literal("~") | Literal("^~")
|
||||
|
||||
# rules
|
||||
comment = Literal('#') + restOfLine()
|
||||
assignment = (key + Optional(space + value, default=None) + semicolon)
|
||||
location_statement = Optional(space + modifier) + Optional(space + location)
|
||||
if_statement = Literal("if") + space + Regex(r"\(.+\)") + space
|
||||
block = Forward()
|
||||
|
||||
block << Group(
|
||||
(Group(key + location_statement) ^ Group(if_statement)) +
|
||||
left_bracket +
|
||||
Group(ZeroOrMore(Group(comment | assignment) | block)) +
|
||||
right_bracket)
|
||||
|
||||
script = OneOrMore(Group(comment | assignment) ^ block) + stringEnd
|
||||
|
||||
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:
|
||||
indentation = spacer * current_indent
|
||||
if isinstance(key, list):
|
||||
if current_indent:
|
||||
yield ''
|
||||
yield indentation + spacer.join(key) + ' {'
|
||||
|
||||
for parameter in values:
|
||||
dumped = self.__iter__([parameter], current_indent + self.indentation)
|
||||
for line in dumped:
|
||||
yield line
|
||||
|
||||
yield indentation + '}'
|
||||
else:
|
||||
if key == '#':
|
||||
yield spacer * current_indent + key + values
|
||||
else:
|
||||
if values is None:
|
||||
yield spacer * current_indent + key + ';'
|
||||
else:
|
||||
yield spacer * current_indent + key + spacer + values + ';'
|
||||
|
||||
def __str__(self):
|
||||
"""Return the parsed block as a string."""
|
||||
return '\n'.join(self) + '\n'
|
||||
|
||||
|
||||
# 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 str(RawNginxDumper(blocks, indentation))
|
||||
|
||||
|
||||
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))
|
||||
@@ -0,0 +1,136 @@
|
||||
"""Module contains classes used by the Nginx Configurator."""
|
||||
import re
|
||||
|
||||
from certbot.plugins import common
|
||||
|
||||
|
||||
class Addr(common.Addr):
|
||||
r"""Represents an Nginx address, i.e. what comes after the 'listen'
|
||||
directive.
|
||||
|
||||
According to the `documentation`_, 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.
|
||||
|
||||
.. _documentation:
|
||||
http://nginx.org/en/docs/http/ngx_http_core_module.html#listen
|
||||
|
||||
.. 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):
|
||||
parts = ''
|
||||
if self.tup[0] and self.tup[1]:
|
||||
parts = "%s:%s" % self.tup
|
||||
elif self.tup[0]:
|
||||
parts = self.tup[0]
|
||||
else:
|
||||
parts = self.tup[1]
|
||||
|
||||
if self.default:
|
||||
parts += ' default_server'
|
||||
if self.ssl:
|
||||
parts += ' ssl'
|
||||
|
||||
return parts
|
||||
|
||||
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 list 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
|
||||
@@ -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";
|
||||
@@ -0,0 +1,535 @@
|
||||
"""NginxParser is a member object of the NginxConfigurator class."""
|
||||
import glob
|
||||
import logging
|
||||
import os
|
||||
import pyparsing
|
||||
import re
|
||||
|
||||
from certbot import errors
|
||||
|
||||
from certbot_nginx import obj
|
||||
from certbot_nginx import nginxparser
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
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.parsed = {}
|
||||
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.
|
||||
|
||||
: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:`~certbot_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 = nginxparser.load(_file)
|
||||
self.parsed[item] = parsed
|
||||
trees.append(parsed)
|
||||
except IOError:
|
||||
logger.warn("Could not open file: %s", item)
|
||||
except pyparsing.ParseException:
|
||||
logger.debug("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.NoInstallationError(
|
||||
"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:
|
||||
logger.debug('Dumping to %s:\n%s', filename, nginxparser.dumps(tree))
|
||||
with open(filename, 'w') as _file:
|
||||
nginxparser.dump(tree, _file)
|
||||
except IOError:
|
||||
logger.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 len(item) > 0 and item[0] == 'server_name':
|
||||
server_names.update(_get_servernames(item[1]))
|
||||
|
||||
return server_names == names
|
||||
|
||||
def add_server_directives(self, filename, names, directives,
|
||||
replace):
|
||||
"""Add or replace directives in the first server block with names.
|
||||
|
||||
..note :: If replace is True, this raises a misconfiguration error
|
||||
if the directive does not already exist.
|
||||
..note :: If replace is False nothing gets added if an identical
|
||||
block exists already.
|
||||
|
||||
..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
|
||||
|
||||
"""
|
||||
try:
|
||||
_do_for_subarray(self.parsed[filename],
|
||||
lambda x: self._has_server_names(x, names),
|
||||
lambda x: _add_directives(x, directives, replace))
|
||||
except errors.MisconfigurationError as err:
|
||||
raise errors.MisconfigurationError("Problem in %s: %s" % (filename, err.message))
|
||||
|
||||
def add_http_directives(self, filename, directives):
|
||||
"""Adds directives to the first encountered HTTP block in filename.
|
||||
|
||||
We insert new directives at the top of the block to work around
|
||||
https://trac.nginx.org/nginx/ticket/810: If the first server block
|
||||
doesn't enable OCSP stapling, stapling is broken for all blocks.
|
||||
|
||||
:param str filename: The absolute filename of the config file
|
||||
:param list directives: The directives to add
|
||||
|
||||
"""
|
||||
_do_for_subarray(self.parsed[filename],
|
||||
lambda x: x[0] == ['http'],
|
||||
lambda x: x[1].insert(0, 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()
|
||||
|
||||
# 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: # pragma: no cover
|
||||
# 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
|
||||
len(entry) == 2 and entry[0] == 'include' 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 = {'addrs': set(),
|
||||
'ssl': False,
|
||||
'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]))
|
||||
elif directive[0] == 'ssl' and directive[1] == 'on':
|
||||
parsed_server['ssl'] = True
|
||||
|
||||
return parsed_server
|
||||
|
||||
|
||||
def _add_directives(block, directives, replace):
|
||||
"""Adds or replaces directives in a config block.
|
||||
|
||||
When replace=False, it's an error to try and add a directive that already
|
||||
exists in the config block with a conflicting value.
|
||||
|
||||
When replace=True, a directive with the same name MUST already exist in the
|
||||
config block, and the first instance will be replaced.
|
||||
|
||||
..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:
|
||||
_add_directive(block, directive, replace)
|
||||
|
||||
repeatable_directives = set(['server_name', 'listen', 'include'])
|
||||
|
||||
def _add_directive(block, directive, replace):
|
||||
"""Adds or replaces a single directive in a config block.
|
||||
|
||||
See _add_directives for more documentation.
|
||||
|
||||
"""
|
||||
location = -1
|
||||
# Find the index of a config line where the name of the directive matches
|
||||
# the name of the directive we want to add.
|
||||
for index, line in enumerate(block):
|
||||
if len(line) > 0 and line[0] == directive[0]:
|
||||
location = index
|
||||
break
|
||||
if replace:
|
||||
if location == -1:
|
||||
raise errors.MisconfigurationError(
|
||||
'expected directive for %s in the Nginx '
|
||||
'config but did not find it.' % directive[0])
|
||||
block[location] = directive
|
||||
else:
|
||||
# Append directive. Fail if the name is not a repeatable directive name,
|
||||
# and there is already a copy of that directive with a different value
|
||||
# in the config file.
|
||||
directive_name = directive[0]
|
||||
directive_value = directive[1]
|
||||
if location != -1 and directive_name.__str__() not in repeatable_directives:
|
||||
if block[location][1] == directive_value:
|
||||
# There's a conflict, but the existing value matches the one we
|
||||
# want to insert, so it's fine.
|
||||
pass
|
||||
else:
|
||||
raise errors.MisconfigurationError(
|
||||
'tried to insert directive "%s" but found conflicting "%s".' % (
|
||||
directive, block[location]))
|
||||
else:
|
||||
block.append(directive)
|
||||
@@ -0,0 +1 @@
|
||||
"""Let's Encrypt Nginx Tests"""
|
||||
@@ -0,0 +1,424 @@
|
||||
# pylint: disable=too-many-public-methods
|
||||
"""Test for certbot_nginx.configurator."""
|
||||
import os
|
||||
import shutil
|
||||
import unittest
|
||||
|
||||
import mock
|
||||
import OpenSSL
|
||||
|
||||
from acme import challenges
|
||||
from acme import messages
|
||||
|
||||
from certbot import achallenges
|
||||
from certbot import errors
|
||||
|
||||
from certbot_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)
|
||||
|
||||
def tearDown(self):
|
||||
shutil.rmtree(self.temp_dir)
|
||||
shutil.rmtree(self.config_dir)
|
||||
shutil.rmtree(self.work_dir)
|
||||
|
||||
@mock.patch("certbot_nginx.configurator.le_util.exe_exists")
|
||||
def test_prepare_no_install(self, mock_exe_exists):
|
||||
mock_exe_exists.return_value = False
|
||||
self.assertRaises(
|
||||
errors.NoInstallationError, self.config.prepare)
|
||||
|
||||
def test_prepare(self):
|
||||
self.assertEquals((1, 6, 2), self.config.version)
|
||||
self.assertEquals(5, len(self.config.parser.parsed))
|
||||
|
||||
@mock.patch("certbot_nginx.configurator.le_util.exe_exists")
|
||||
@mock.patch("certbot_nginx.configurator.subprocess.Popen")
|
||||
def test_prepare_initializes_version(self, mock_popen, mock_exe_exists):
|
||||
mock_popen().communicate.return_value = (
|
||||
"", "\n".join(["nginx version: nginx/1.6.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"]))
|
||||
|
||||
mock_exe_exists.return_value = True
|
||||
|
||||
self.config.version = None
|
||||
self.config.config_test = mock.Mock()
|
||||
self.config.prepare()
|
||||
self.assertEquals((1, 6, 2), self.config.version)
|
||||
|
||||
@mock.patch("certbot_nginx.configurator.socket.gethostbyaddr")
|
||||
def test_get_all_names(self, mock_gethostbyaddr):
|
||||
mock_gethostbyaddr.return_value = ('155.225.50.69.nephoscale.net', [], [])
|
||||
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(['redirect'], self.config.supported_enhancements())
|
||||
|
||||
def test_enhance(self):
|
||||
self.assertRaises(
|
||||
errors.PluginError, self.config.enhance, 'myhost', 'unknown_enhancement')
|
||||
|
||||
def test_get_chall_pref(self):
|
||||
self.assertEqual([challenges.TLSSNI01],
|
||||
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', '5001 ssl']],
|
||||
replace=False)
|
||||
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', '5001 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}
|
||||
|
||||
conf_path = {'localhost': "etc_nginx/nginx.conf",
|
||||
'alias': "etc_nginx/nginx.conf",
|
||||
'example.com': "etc_nginx/sites-enabled/example.com",
|
||||
'example.com.uk.test': "etc_nginx/sites-enabled/example.com",
|
||||
'www.example.com': "etc_nginx/sites-enabled/example.com",
|
||||
'test.www.example.com': "etc_nginx/foo.conf",
|
||||
'abc.www.foo.com': "etc_nginx/foo.conf",
|
||||
'www.bar.co.uk': "etc_nginx/nginx.conf"}
|
||||
|
||||
bad_results = ['www.foo.com', 'example', 't.www.bar.co',
|
||||
'69.255.225.155']
|
||||
|
||||
for name in results:
|
||||
vhost = self.config.choose_vhost(name)
|
||||
path = os.path.relpath(vhost.filep, self.temp_dir)
|
||||
|
||||
self.assertEqual(results[name], vhost.names)
|
||||
self.assertEqual(conf_path[name], path)
|
||||
|
||||
for name in bad_results:
|
||||
self.assertEqual(set([name]), self.config.choose_vhost(name).names)
|
||||
|
||||
def test_more_info(self):
|
||||
self.assertTrue('nginx.conf' in self.config.more_info())
|
||||
|
||||
def test_deploy_cert_stapling(self):
|
||||
# Choose a version of Nginx greater than 1.3.7 so stapling code gets
|
||||
# invoked.
|
||||
self.config.version = (1, 9, 6)
|
||||
example_conf = self.config.parser.abs_path('sites-enabled/example.com')
|
||||
self.config.deploy_cert(
|
||||
"www.example.com",
|
||||
"example/cert.pem",
|
||||
"example/key.pem",
|
||||
"example/chain.pem",
|
||||
"example/fullchain.pem")
|
||||
self.config.save()
|
||||
self.config.parser.load()
|
||||
generated_conf = self.config.parser.parsed[example_conf]
|
||||
|
||||
self.assertTrue(util.contains_at_depth(generated_conf,
|
||||
['ssl_stapling', 'on'], 2))
|
||||
self.assertTrue(util.contains_at_depth(generated_conf,
|
||||
['ssl_stapling_verify', 'on'], 2))
|
||||
self.assertTrue(util.contains_at_depth(generated_conf,
|
||||
['ssl_trusted_certificate', 'example/chain.pem'], 2))
|
||||
|
||||
def test_deploy_cert_stapling_requires_chain_path(self):
|
||||
self.config.version = (1, 3, 7)
|
||||
self.assertRaises(errors.PluginError, self.config.deploy_cert,
|
||||
"www.example.com",
|
||||
"example/cert.pem",
|
||||
"example/key.pem",
|
||||
None,
|
||||
"example/fullchain.pem")
|
||||
|
||||
def test_deploy_cert_requires_fullchain_path(self):
|
||||
self.config.version = (1, 3, 1)
|
||||
self.assertRaises(errors.PluginError, self.config.deploy_cert,
|
||||
"www.example.com",
|
||||
"example/cert.pem",
|
||||
"example/key.pem",
|
||||
"example/chain.pem",
|
||||
None)
|
||||
|
||||
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')
|
||||
# Choose a version of Nginx less than 1.3.7 so stapling code doesn't get
|
||||
# invoked.
|
||||
self.config.version = (1, 3, 1)
|
||||
|
||||
# Get the default SSL vhost
|
||||
self.config.deploy_cert(
|
||||
"www.example.com",
|
||||
"example/cert.pem",
|
||||
"example/key.pem",
|
||||
"example/chain.pem",
|
||||
"example/fullchain.pem")
|
||||
self.config.deploy_cert(
|
||||
"another.alias",
|
||||
"/etc/nginx/cert.pem",
|
||||
"/etc/nginx/key.pem",
|
||||
"/etc/nginx/chain.pem",
|
||||
"/etc/nginx/fullchain.pem")
|
||||
self.config.save()
|
||||
|
||||
self.config.parser.load()
|
||||
|
||||
parsed_example_conf = util.filter_comments(self.config.parser.parsed[example_conf])
|
||||
parsed_server_conf = util.filter_comments(self.config.parser.parsed[server_conf])
|
||||
parsed_nginx_conf = util.filter_comments(self.config.parser.parsed[nginx_conf])
|
||||
|
||||
self.assertEqual([[['server'],
|
||||
[
|
||||
['listen', '69.50.225.155:9000'],
|
||||
['listen', '127.0.0.1'],
|
||||
['server_name', '.example.com'],
|
||||
['server_name', 'example.*'],
|
||||
|
||||
['listen', '5001 ssl'],
|
||||
['ssl_certificate', 'example/fullchain.pem'],
|
||||
['ssl_certificate_key', 'example/key.pem'],
|
||||
['include', self.config.parser.loc["ssl_options"]]
|
||||
]]],
|
||||
parsed_example_conf)
|
||||
self.assertEqual([['server_name', 'somename alias another.alias']],
|
||||
parsed_server_conf)
|
||||
self.assertTrue(util.contains_at_depth(
|
||||
parsed_nginx_conf,
|
||||
[['server'],
|
||||
[
|
||||
['listen', '8000'],
|
||||
['listen', 'somename:8080'],
|
||||
['include', 'server.conf'],
|
||||
[['location', '/'],
|
||||
[['root', 'html'],
|
||||
['index', 'index.html index.htm']]],
|
||||
['listen', '5001 ssl'],
|
||||
['ssl_certificate', '/etc/nginx/fullchain.pem'],
|
||||
['ssl_certificate_key', '/etc/nginx/key.pem'],
|
||||
['include', self.config.parser.loc["ssl_options"]]]],
|
||||
2))
|
||||
|
||||
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 SSL vhost
|
||||
self.config.deploy_cert(
|
||||
"www.example.com",
|
||||
"example/cert.pem",
|
||||
"example/key.pem",
|
||||
"example/chain.pem",
|
||||
"example/fullchain.pem")
|
||||
self.config.deploy_cert(
|
||||
"another.alias",
|
||||
"/etc/nginx/cert.pem",
|
||||
"/etc/nginx/key.pem",
|
||||
"/etc/nginx/chain.pem",
|
||||
"/etc/nginx/fullchain.pem")
|
||||
self.config.save()
|
||||
|
||||
self.config.parser.load()
|
||||
self.assertEqual(set([
|
||||
('example/fullchain.pem', 'example/key.pem', example_conf),
|
||||
('/etc/nginx/fullchain.pem', '/etc/nginx/key.pem', nginx_conf),
|
||||
]), self.config.get_all_certs_keys())
|
||||
|
||||
@mock.patch("certbot_nginx.configurator.tls_sni_01.NginxTlsSni01.perform")
|
||||
@mock.patch("certbot_nginx.configurator.NginxConfigurator.restart")
|
||||
def test_perform(self, mock_restart, mock_perform):
|
||||
# Only tests functionality specific to configurator.perform
|
||||
# Note: As more challenges are offered this will have to be expanded
|
||||
achall1 = achallenges.KeyAuthorizationAnnotatedChallenge(
|
||||
challb=messages.ChallengeBody(
|
||||
chall=challenges.TLSSNI01(token="kNdwjwOeX0I_A8DXt9Msmg"),
|
||||
uri="https://ca.org/chall0_uri",
|
||||
status=messages.Status("pending"),
|
||||
), domain="localhost", account_key=self.rsa512jwk)
|
||||
achall2 = achallenges.KeyAuthorizationAnnotatedChallenge(
|
||||
challb=messages.ChallengeBody(
|
||||
chall=challenges.TLSSNI01(token="m8TdO1qik4JVFtgPPurJmg"),
|
||||
uri="https://ca.org/chall1_uri",
|
||||
status=messages.Status("pending"),
|
||||
), domain="example.com", account_key=self.rsa512jwk)
|
||||
|
||||
expected = [
|
||||
achall1.response(self.rsa512jwk),
|
||||
achall2.response(self.rsa512jwk),
|
||||
]
|
||||
|
||||
mock_perform.return_value = expected
|
||||
responses = self.config.perform([achall1, achall2])
|
||||
|
||||
self.assertEqual(mock_perform.call_count, 1)
|
||||
self.assertEqual(responses, expected)
|
||||
self.assertEqual(mock_restart.call_count, 1)
|
||||
|
||||
@mock.patch("certbot_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.PluginError, self.config.get_version)
|
||||
|
||||
mock_popen().communicate.return_value = (
|
||||
"", "\n".join(["nginx version: nginx/1.4.2",
|
||||
"TLS SNI support enabled"]))
|
||||
self.assertRaises(errors.PluginError, 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.PluginError, 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.NotSupportedError, self.config.get_version)
|
||||
|
||||
mock_popen.side_effect = OSError("Can't find program")
|
||||
self.assertRaises(errors.PluginError, self.config.get_version)
|
||||
|
||||
@mock.patch("certbot_nginx.configurator.subprocess.Popen")
|
||||
def test_nginx_restart(self, mock_popen):
|
||||
mocked = mock_popen()
|
||||
mocked.communicate.return_value = ('', '')
|
||||
mocked.returncode = 0
|
||||
self.config.restart()
|
||||
|
||||
@mock.patch("certbot_nginx.configurator.subprocess.Popen")
|
||||
def test_nginx_restart_fail(self, mock_popen):
|
||||
mocked = mock_popen()
|
||||
mocked.communicate.return_value = ('', '')
|
||||
mocked.returncode = 1
|
||||
self.assertRaises(errors.MisconfigurationError, self.config.restart)
|
||||
|
||||
@mock.patch("certbot_nginx.configurator.subprocess.Popen")
|
||||
def test_no_nginx_start(self, mock_popen):
|
||||
mock_popen.side_effect = OSError("Can't find program")
|
||||
self.assertRaises(errors.MisconfigurationError, self.config.restart)
|
||||
|
||||
@mock.patch("certbot.le_util.run_script")
|
||||
def test_config_test(self, _):
|
||||
self.config.config_test()
|
||||
|
||||
@mock.patch("certbot.le_util.run_script")
|
||||
def test_config_test_bad_process(self, mock_run_script):
|
||||
mock_run_script.side_effect = errors.SubprocessError
|
||||
self.assertRaises(errors.MisconfigurationError, self.config.config_test)
|
||||
|
||||
@mock.patch("certbot.reverter.Reverter.recovery_routine")
|
||||
def test_recovery_routine_throws_error_from_reverter(self, mock_recovery_routine):
|
||||
mock_recovery_routine.side_effect = errors.ReverterError("foo")
|
||||
self.assertRaises(errors.PluginError, self.config.recovery_routine)
|
||||
|
||||
@mock.patch("certbot.reverter.Reverter.view_config_changes")
|
||||
def test_view_config_changes_throws_error_from_reverter(self, mock_view_config_changes):
|
||||
mock_view_config_changes.side_effect = errors.ReverterError("foo")
|
||||
self.assertRaises(errors.PluginError, self.config.view_config_changes)
|
||||
|
||||
@mock.patch("certbot.reverter.Reverter.rollback_checkpoints")
|
||||
def test_rollback_checkpoints_throws_error_from_reverter(self, mock_rollback_checkpoints):
|
||||
mock_rollback_checkpoints.side_effect = errors.ReverterError("foo")
|
||||
self.assertRaises(errors.PluginError, self.config.rollback_checkpoints)
|
||||
|
||||
@mock.patch("certbot.reverter.Reverter.revert_temporary_config")
|
||||
def test_revert_challenge_config_throws_error_from_reverter(self, mock_revert_temporary_config):
|
||||
mock_revert_temporary_config.side_effect = errors.ReverterError("foo")
|
||||
self.assertRaises(errors.PluginError, self.config.revert_challenge_config)
|
||||
|
||||
@mock.patch("certbot.reverter.Reverter.add_to_checkpoint")
|
||||
def test_save_throws_error_from_reverter(self, mock_add_to_checkpoint):
|
||||
mock_add_to_checkpoint.side_effect = errors.ReverterError("foo")
|
||||
self.assertRaises(errors.PluginError, self.config.save)
|
||||
|
||||
def test_get_snakeoil_paths(self):
|
||||
# pylint: disable=protected-access
|
||||
cert, key = self.config._get_snakeoil_paths()
|
||||
self.assertTrue(os.path.exists(cert))
|
||||
self.assertTrue(os.path.exists(key))
|
||||
with open(cert) as cert_file:
|
||||
OpenSSL.crypto.load_certificate(
|
||||
OpenSSL.crypto.FILETYPE_PEM, cert_file.read())
|
||||
with open(key) as key_file:
|
||||
OpenSSL.crypto.load_privatekey(
|
||||
OpenSSL.crypto.FILETYPE_PEM, key_file.read())
|
||||
|
||||
def test_redirect_enhance(self):
|
||||
expected = [
|
||||
['if', '($scheme != "https")'],
|
||||
[['return', '301 https://$host$request_uri']]
|
||||
]
|
||||
|
||||
example_conf = self.config.parser.abs_path('sites-enabled/example.com')
|
||||
self.config.enhance("www.example.com", "redirect")
|
||||
|
||||
generated_conf = self.config.parser.parsed[example_conf]
|
||||
self.assertTrue(util.contains_at_depth(generated_conf, expected, 2))
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main() # pragma: no cover
|
||||
@@ -0,0 +1,172 @@
|
||||
"""Test for certbot_nginx.nginxparser."""
|
||||
import operator
|
||||
import unittest
|
||||
|
||||
from pyparsing import ParseException
|
||||
|
||||
from certbot_nginx.nginxparser import (
|
||||
RawNginxParser, loads, load, dumps, dump)
|
||||
from certbot_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', None],
|
||||
[['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'
|
||||
'}\n')
|
||||
|
||||
def test_parse_from_file(self):
|
||||
with open(util.get_data_filename('foo.conf')) as handle:
|
||||
parsed = util.filter_comments(load(handle))
|
||||
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_parse_from_file2(self):
|
||||
with open(util.get_data_filename('edge_cases.conf')) as handle:
|
||||
parsed = util.filter_comments(load(handle))
|
||||
self.assertEqual(
|
||||
parsed,
|
||||
[[['server'], [['server_name', 'simple']]],
|
||||
[['server'],
|
||||
[['server_name', 'with.if'],
|
||||
[['location', '~', '^/services/.+$'],
|
||||
[[['if', '($request_filename ~* \\.(ttf|woff)$)'],
|
||||
[['add_header', 'Access-Control-Allow-Origin "*"']]]]]]],
|
||||
[['server'],
|
||||
[['server_name', 'with.complicated.headers'],
|
||||
[['location', '~*', '\\.(?:gif|jpe?g|png)$'],
|
||||
[['add_header', 'Pragma public'],
|
||||
['add_header',
|
||||
'Cache-Control \'public, must-revalidate, proxy-revalidate\''
|
||||
' "test,;{}" foo'],
|
||||
['blah', '"hello;world"'],
|
||||
['try_files', '$uri @rewrites']]]]]])
|
||||
|
||||
def test_abort_on_parse_failure(self):
|
||||
with open(util.get_data_filename('broken.conf')) as handle:
|
||||
self.assertRaises(ParseException, load, handle)
|
||||
|
||||
def test_dump_as_file(self):
|
||||
with open(util.get_data_filename('nginx.conf')) as handle:
|
||||
parsed = util.filter_comments(load(handle))
|
||||
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']]]]])
|
||||
|
||||
with open(util.get_data_filename('nginx.new.conf'), 'w') as handle:
|
||||
dump(parsed, handle)
|
||||
with open(util.get_data_filename('nginx.new.conf')) as handle:
|
||||
parsed_new = util.filter_comments(load(handle))
|
||||
self.assertEquals(parsed, parsed_new)
|
||||
|
||||
def test_comments(self):
|
||||
with open(util.get_data_filename('minimalistic_comments.conf')) as handle:
|
||||
parsed = load(handle)
|
||||
|
||||
with open(util.get_data_filename('minimalistic_comments.new.conf'), 'w') as handle:
|
||||
dump(parsed, handle)
|
||||
|
||||
with open(util.get_data_filename('minimalistic_comments.new.conf')) as handle:
|
||||
parsed_new = load(handle)
|
||||
|
||||
self.assertEquals(parsed, parsed_new)
|
||||
|
||||
self.assertEqual(parsed_new, [
|
||||
['#', " Use bar.conf when it's a full moon!"],
|
||||
['include', 'foo.conf'],
|
||||
['#', ' Kilroy was here'],
|
||||
['check_status', None],
|
||||
[['server'],
|
||||
[['#', ''],
|
||||
['#', " Don't forget to open up your firewall!"],
|
||||
['#', ''],
|
||||
['listen', '1234'],
|
||||
['#', ' listen 80;']]],
|
||||
])
|
||||
|
||||
def test_issue_518(self):
|
||||
parsed = loads('if ($http_accept ~* "webp") { set $webp "true"; }')
|
||||
|
||||
self.assertEqual(parsed, [
|
||||
[['if', '($http_accept ~* "webp")'],
|
||||
[['set', '$webp "true"']]]
|
||||
])
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main() # pragma: no cover
|
||||
@@ -0,0 +1,105 @@
|
||||
"""Test the helper objects in certbot_nginx.obj."""
|
||||
import unittest
|
||||
|
||||
|
||||
class AddrTest(unittest.TestCase):
|
||||
"""Test the Addr class."""
|
||||
def setUp(self):
|
||||
from certbot_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:* ssl")
|
||||
self.assertEqual(str(self.addr3), "192.168.1.1:80")
|
||||
self.assertEqual(str(self.addr4), "*:80 default_server ssl")
|
||||
self.assertEqual(str(self.addr5), "myhost")
|
||||
self.assertEqual(str(self.addr6), "80 default_server")
|
||||
|
||||
def test_eq(self):
|
||||
from certbot_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 certbot_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 certbot_nginx.obj import VirtualHost
|
||||
from certbot_nginx.obj import Addr
|
||||
self.vhost1 = VirtualHost(
|
||||
"filep",
|
||||
set([Addr.fromstring("localhost")]), False, False,
|
||||
set(['localhost']), [])
|
||||
|
||||
def test_eq(self):
|
||||
from certbot_nginx.obj import Addr
|
||||
from certbot_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() # pragma: no cover
|
||||
@@ -0,0 +1,253 @@
|
||||
"""Tests for certbot_nginx.parser."""
|
||||
import glob
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import unittest
|
||||
|
||||
from certbot import errors
|
||||
|
||||
from certbot_nginx import nginxparser
|
||||
from certbot_nginx import obj
|
||||
from certbot_nginx import parser
|
||||
from certbot_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, "etc_nginx/////"
|
||||
"ubuntu_nginx/../../etc_nginx")
|
||||
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']],
|
||||
replace=False)
|
||||
ssl_re = re.compile(r'\n\s+ssl_certificate /etc/ssl/cert.pem')
|
||||
dump = nginxparser.dumps(nparser.parsed[nparser.abs_path('nginx.conf')])
|
||||
self.assertEqual(1, len(re.findall(ssl_re, dump)))
|
||||
|
||||
server_conf = nparser.abs_path('server.conf')
|
||||
names = set(['alias', 'another.alias', 'somename'])
|
||||
nparser.add_server_directives(server_conf, names,
|
||||
[['foo', 'bar'], ['ssl_certificate',
|
||||
'/etc/ssl/cert2.pem']],
|
||||
replace=False)
|
||||
nparser.add_server_directives(server_conf, names, [['foo', 'bar']],
|
||||
replace=False)
|
||||
self.assertEqual(nparser.parsed[server_conf],
|
||||
[['server_name', 'somename alias another.alias'],
|
||||
['foo', 'bar'],
|
||||
['ssl_certificate', '/etc/ssl/cert2.pem']
|
||||
])
|
||||
|
||||
def test_add_http_directives(self):
|
||||
nparser = parser.NginxParser(self.config_path, self.ssl_options)
|
||||
filep = nparser.abs_path('nginx.conf')
|
||||
block = [['server'],
|
||||
[['listen', '80'],
|
||||
['server_name', 'localhost']]]
|
||||
nparser.add_http_directives(filep, block)
|
||||
root = nparser.parsed[filep]
|
||||
self.assertTrue(util.contains_at_depth(root, ['http'], 1))
|
||||
self.assertTrue(util.contains_at_depth(root, block, 2))
|
||||
|
||||
# Check that our server block got inserted first among all server
|
||||
# blocks.
|
||||
http_block = [x for x in root if x[0] == ['http']][0][1]
|
||||
server_blocks = [x for x in http_block if x[0] == ['server']]
|
||||
self.assertEqual(server_blocks[0], block)
|
||||
|
||||
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', 'foobar.com']], replace=True)
|
||||
self.assertEqual(
|
||||
nparser.parsed[filep],
|
||||
[[['server'], [['listen', '69.50.225.155:9000'],
|
||||
['listen', '127.0.0.1'],
|
||||
['server_name', 'foobar.com'],
|
||||
['server_name', 'example.*'],
|
||||
]]])
|
||||
self.assertRaises(errors.MisconfigurationError,
|
||||
nparser.add_server_directives,
|
||||
filep, set(['foobar.com', 'example.*']),
|
||||
[['ssl_certificate', 'cert.pem']],
|
||||
replace=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']],
|
||||
replace=False)
|
||||
c_k = nparser.get_all_certs_keys()
|
||||
self.assertEqual(set([('foo.pem', 'bar.key', filep)]), c_k)
|
||||
|
||||
def test_parse_server_ssl(self):
|
||||
server = parser.parse_server([
|
||||
['listen', '443']
|
||||
])
|
||||
self.assertFalse(server['ssl'])
|
||||
|
||||
server = parser.parse_server([
|
||||
['listen', '443 ssl']
|
||||
])
|
||||
self.assertTrue(server['ssl'])
|
||||
|
||||
server = parser.parse_server([
|
||||
['listen', '443'], ['ssl', 'off']
|
||||
])
|
||||
self.assertFalse(server['ssl'])
|
||||
|
||||
server = parser.parse_server([
|
||||
['listen', '443'], ['ssl', 'on']
|
||||
])
|
||||
self.assertTrue(server['ssl'])
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main() # pragma: no cover
|
||||
@@ -0,0 +1,12 @@
|
||||
# A faulty configuration file
|
||||
|
||||
pid logs/nginx.pid;
|
||||
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
include foo.conf;
|
||||
|
||||
@@@
|
||||
@@ -0,0 +1,27 @@
|
||||
# This is not a valid nginx config file but it tests edge cases in valid nginx syntax
|
||||
|
||||
server {
|
||||
server_name simple;
|
||||
}
|
||||
|
||||
server {
|
||||
server_name with.if;
|
||||
location ~ ^/services/.+$ {
|
||||
if ($request_filename ~* \.(ttf|woff)$) {
|
||||
add_header Access-Control-Allow-Origin "*";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
server_name with.complicated.headers;
|
||||
|
||||
location ~* \.(?:gif|jpe?g|png)$ {
|
||||
|
||||
add_header Pragma public;
|
||||
add_header Cache-Control 'public, must-revalidate, proxy-revalidate' "test,;{}" foo;
|
||||
blah "hello;world";
|
||||
|
||||
try_files $uri @rewrites;
|
||||
}
|
||||
}
|
||||
@@ -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$ {}
|
||||
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
# Use bar.conf when it's a full moon!
|
||||
include foo.conf; # Kilroy was here
|
||||
check_status;
|
||||
|
||||
server {
|
||||
#
|
||||
# Don't forget to open up your firewall!
|
||||
#
|
||||
listen 1234;
|
||||
# listen 80;
|
||||
}
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
# Use bar.conf when it's a full moon!
|
||||
include foo.conf;
|
||||
# Kilroy was here
|
||||
check_status;
|
||||
server {
|
||||
#
|
||||
# Don't forget to open up your firewall!
|
||||
#
|
||||
listen 1234;
|
||||
# listen 80;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
server_name somename alias another.alias;
|
||||
@@ -0,0 +1,9 @@
|
||||
server {
|
||||
listen myhost default_server;
|
||||
server_name www.example.org;
|
||||
|
||||
location / {
|
||||
root html;
|
||||
index index.html index.htm;
|
||||
}
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
server {
|
||||
listen 69.50.225.155:9000;
|
||||
listen 127.0.0.1;
|
||||
server_name .example.com;
|
||||
server_name example.*;
|
||||
}
|
||||
+25
@@ -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;
|
||||
Vendored
+108
@@ -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
|
||||
}
|
||||
Vendored
+102
@@ -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
|
||||
}
|
||||
+79
@@ -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;
|
||||
}
|
||||
+16
@@ -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
|
||||
|
||||
+13
@@ -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;
|
||||
+75
@@ -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;
|
||||
+95
@@ -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;
|
||||
# }
|
||||
#}
|
||||
+4
@@ -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;
|
||||
+14
@@ -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;
|
||||
+112
@@ -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;
|
||||
# }
|
||||
#}
|
||||
Vendored
Symlink
+1
@@ -0,0 +1 @@
|
||||
../sites-available/default
|
||||
+15
@@ -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;
|
||||
Vendored
+125
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
"""Tests for certbot_nginx.tls_sni_01"""
|
||||
import unittest
|
||||
import shutil
|
||||
|
||||
import mock
|
||||
|
||||
from acme import challenges
|
||||
|
||||
from certbot import achallenges
|
||||
from certbot import errors
|
||||
|
||||
from certbot.plugins import common_test
|
||||
from certbot.tests import acme_util
|
||||
|
||||
from certbot_nginx import obj
|
||||
from certbot_nginx.tests import util
|
||||
|
||||
|
||||
class TlsSniPerformTest(util.NginxTest):
|
||||
"""Test the NginxTlsSni01 challenge."""
|
||||
|
||||
account_key = common_test.TLSSNI01Test.auth_key
|
||||
achalls = [
|
||||
achallenges.KeyAuthorizationAnnotatedChallenge(
|
||||
challb=acme_util.chall_to_challb(
|
||||
challenges.TLSSNI01(token="kNdwjwOeX0I_A8DXt9Msmg"), "pending"),
|
||||
domain="www.example.com", account_key=account_key),
|
||||
achallenges.KeyAuthorizationAnnotatedChallenge(
|
||||
challb=acme_util.chall_to_challb(
|
||||
challenges.TLSSNI01(
|
||||
token="\xba\xa9\xda?<m\xaewmx\xea\xad\xadv\xf4\x02\xc9y"
|
||||
"\x80\xe2_X\t\xe7\xc7\xa4\t\xca\xf7&\x945"
|
||||
), "pending"),
|
||||
domain="blah", account_key=account_key),
|
||||
achallenges.KeyAuthorizationAnnotatedChallenge(
|
||||
challb=acme_util.chall_to_challb(
|
||||
challenges.TLSSNI01(
|
||||
token="\x8c\x8a\xbf_-f\\cw\xee\xd6\xf8/\xa5\xe3\xfd"
|
||||
"\xeb9\xf1\xf5\xb9\xefVM\xc9w\xa4u\x9c\xe1\x87\xb4"
|
||||
), "pending"),
|
||||
domain="www.example.org", account_key=account_key),
|
||||
]
|
||||
|
||||
def setUp(self):
|
||||
super(TlsSniPerformTest, self).setUp()
|
||||
|
||||
config = util.get_nginx_configurator(
|
||||
self.config_path, self.config_dir, self.work_dir)
|
||||
|
||||
from certbot_nginx import tls_sni_01
|
||||
self.sni = tls_sni_01.NginxTlsSni01(config)
|
||||
|
||||
def tearDown(self):
|
||||
shutil.rmtree(self.temp_dir)
|
||||
shutil.rmtree(self.config_dir)
|
||||
shutil.rmtree(self.work_dir)
|
||||
|
||||
@mock.patch("certbot_nginx.configurator"
|
||||
".NginxConfigurator.choose_vhost")
|
||||
def test_perform(self, mock_choose):
|
||||
self.sni.add_chall(self.achalls[1])
|
||||
mock_choose.return_value = None
|
||||
result = self.sni.perform()
|
||||
self.assertTrue(result is None)
|
||||
|
||||
def test_perform0(self):
|
||||
responses = self.sni.perform()
|
||||
self.assertEqual([], responses)
|
||||
|
||||
@mock.patch("certbot_nginx.configurator.NginxConfigurator.save")
|
||||
def test_perform1(self, mock_save):
|
||||
self.sni.add_chall(self.achalls[0])
|
||||
response = self.achalls[0].response(self.account_key)
|
||||
mock_setup_cert = mock.MagicMock(return_value=response)
|
||||
|
||||
# pylint: disable=protected-access
|
||||
self.sni._setup_challenge_cert = mock_setup_cert
|
||||
|
||||
responses = self.sni.perform()
|
||||
|
||||
mock_setup_cert.assert_called_once_with(self.achalls[0])
|
||||
self.assertEqual([response], responses)
|
||||
self.assertEqual(mock_save.call_count, 2)
|
||||
|
||||
# Make sure challenge config is included in main config
|
||||
http = self.sni.configurator.parser.parsed[
|
||||
self.sni.configurator.parser.loc["root"]][-1]
|
||||
self.assertTrue(
|
||||
util.contains_at_depth(http, ['include', self.sni.challenge_conf], 1))
|
||||
|
||||
def test_perform2(self):
|
||||
acme_responses = []
|
||||
for achall in self.achalls:
|
||||
self.sni.add_chall(achall)
|
||||
acme_responses.append(achall.response(self.account_key))
|
||||
|
||||
mock_setup_cert = mock.MagicMock(side_effect=acme_responses)
|
||||
# pylint: disable=protected-access
|
||||
self.sni._setup_challenge_cert = mock_setup_cert
|
||||
|
||||
sni_responses = self.sni.perform()
|
||||
|
||||
self.assertEqual(mock_setup_cert.call_count, 3)
|
||||
|
||||
for index, achall in enumerate(self.achalls):
|
||||
self.assertEqual(
|
||||
mock_setup_cert.call_args_list[index], mock.call(achall))
|
||||
|
||||
http = self.sni.configurator.parser.parsed[
|
||||
self.sni.configurator.parser.loc["root"]][-1]
|
||||
self.assertTrue(['include', self.sni.challenge_conf] in http[1])
|
||||
self.assertTrue(
|
||||
util.contains_at_depth(http, ['server_name', 'blah'], 3))
|
||||
|
||||
self.assertEqual(len(sni_responses), 3)
|
||||
for i in xrange(3):
|
||||
self.assertEqual(sni_responses[i], acme_responses[i])
|
||||
|
||||
def test_mod_config(self):
|
||||
self.sni.add_chall(self.achalls[0])
|
||||
self.sni.add_chall(self.achalls[2])
|
||||
|
||||
v_addr1 = [obj.Addr("69.50.225.155", "9000", True, False),
|
||||
obj.Addr("127.0.0.1", "", False, False)]
|
||||
v_addr2 = [obj.Addr("myhost", "", False, True)]
|
||||
ll_addr = [v_addr1, v_addr2]
|
||||
self.sni._mod_config(ll_addr) # pylint: disable=protected-access
|
||||
|
||||
self.sni.configurator.save()
|
||||
|
||||
self.sni.configurator.parser.load()
|
||||
|
||||
http = self.sni.configurator.parser.parsed[
|
||||
self.sni.configurator.parser.loc["root"]][-1]
|
||||
self.assertTrue(['include', self.sni.challenge_conf] in http[1])
|
||||
|
||||
vhosts = self.sni.configurator.parser.get_vhosts()
|
||||
vhs = [vh for vh in vhosts if vh.filep == self.sni.challenge_conf]
|
||||
|
||||
for vhost in vhs:
|
||||
if vhost.addrs == set(v_addr1):
|
||||
response = self.achalls[0].response(self.account_key)
|
||||
else:
|
||||
response = self.achalls[2].response(self.account_key)
|
||||
self.assertEqual(vhost.addrs, set(v_addr2))
|
||||
self.assertEqual(vhost.names, set([response.z_domain]))
|
||||
|
||||
self.assertEqual(len(vhs), 2)
|
||||
|
||||
def test_mod_config_fail(self):
|
||||
root = self.sni.configurator.parser.loc["root"]
|
||||
self.sni.configurator.parser.parsed[root] = [['include', 'foo.conf']]
|
||||
# pylint: disable=protected-access
|
||||
self.assertRaises(
|
||||
errors.MisconfigurationError, self.sni._mod_config, [])
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main() # pragma: no cover
|
||||
@@ -0,0 +1,111 @@
|
||||
"""Common utilities for certbot_nginx."""
|
||||
import os
|
||||
import pkg_resources
|
||||
import unittest
|
||||
|
||||
import mock
|
||||
import zope.component
|
||||
|
||||
from acme import jose
|
||||
|
||||
from certbot import configuration
|
||||
|
||||
from certbot.tests import test_util
|
||||
|
||||
from certbot.plugins import common
|
||||
|
||||
from certbot_nginx import constants
|
||||
from certbot_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 = common.dir_setup(
|
||||
"etc_nginx", "certbot_nginx.tests")
|
||||
|
||||
self.ssl_options = common.setup_ssl_options(
|
||||
self.config_dir, constants.MOD_SSL_CONF_SRC,
|
||||
constants.MOD_SSL_CONF_DEST)
|
||||
|
||||
self.config_path = os.path.join(self.temp_dir, "etc_nginx")
|
||||
|
||||
self.rsa512jwk = jose.JWKRSA.load(test_util.load_vector(
|
||||
"rsa512_key.pem"))
|
||||
|
||||
|
||||
def get_data_filename(filename):
|
||||
"""Gets the filename of a test data file."""
|
||||
return pkg_resources.resource_filename(
|
||||
"certbot_nginx.tests", os.path.join(
|
||||
"testdata", "etc_nginx", filename))
|
||||
|
||||
|
||||
def get_nginx_configurator(
|
||||
config_path, config_dir, work_dir, version=(1, 6, 2)):
|
||||
"""Create an Nginx Configurator with the specified options."""
|
||||
|
||||
backups = os.path.join(work_dir, "backups")
|
||||
|
||||
with mock.patch("certbot_nginx.configurator.NginxConfigurator."
|
||||
"config_test"):
|
||||
with mock.patch("certbot_nginx.configurator.le_util."
|
||||
"exe_exists") as mock_exe_exists:
|
||||
mock_exe_exists.return_value = True
|
||||
config = configurator.NginxConfigurator(
|
||||
config=mock.MagicMock(
|
||||
nginx_server_root=config_path,
|
||||
le_vhost_ext="-le-ssl.conf",
|
||||
config_dir=config_dir,
|
||||
work_dir=work_dir,
|
||||
backup_dir=backups,
|
||||
temp_checkpoint_dir=os.path.join(work_dir, "temp_checkpoints"),
|
||||
in_progress_dir=os.path.join(backups, "IN_PROGRESS"),
|
||||
server="https://acme-server.org:443/new",
|
||||
tls_sni_01_port=5001,
|
||||
),
|
||||
name="nginx",
|
||||
version=version)
|
||||
config.prepare()
|
||||
|
||||
# Provide general config utility.
|
||||
nsconfig = configuration.NamespaceConfig(config.config)
|
||||
zope.component.provideUtility(nsconfig)
|
||||
|
||||
return config
|
||||
|
||||
|
||||
def filter_comments(tree):
|
||||
"""Filter comment nodes from parsed configurations."""
|
||||
|
||||
def traverse(tree):
|
||||
"""Generator dropping comment nodes"""
|
||||
for key, values in tree:
|
||||
if isinstance(key, list):
|
||||
yield [key, filter_comments(values)]
|
||||
else:
|
||||
if key != '#':
|
||||
yield [key, values]
|
||||
|
||||
return list(traverse(tree))
|
||||
|
||||
|
||||
def contains_at_depth(haystack, needle, n):
|
||||
"""Is the needle in haystack at depth n?
|
||||
|
||||
Return true if the needle is present in one of the sub-iterables in haystack
|
||||
at depth n. Haystack must be an iterable.
|
||||
"""
|
||||
# Specifically use hasattr rather than isinstance(..., collections.Iterable)
|
||||
# because we want to include lists but reject strings.
|
||||
if not hasattr(haystack, '__iter__'):
|
||||
return False
|
||||
if n == 0:
|
||||
return needle in haystack
|
||||
else:
|
||||
for item in haystack:
|
||||
if contains_at_depth(item, needle, n - 1):
|
||||
return True
|
||||
return False
|
||||
@@ -0,0 +1,160 @@
|
||||
"""A class that performs TLS-SNI-01 challenges for Nginx"""
|
||||
|
||||
import itertools
|
||||
import logging
|
||||
import os
|
||||
|
||||
from certbot import errors
|
||||
from certbot.plugins import common
|
||||
|
||||
from certbot_nginx import obj
|
||||
from certbot_nginx import nginxparser
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class NginxTlsSni01(common.TLSSNI01):
|
||||
"""TLS-SNI-01 authenticator for Nginx
|
||||
|
||||
:ivar configurator: NginxConfigurator object
|
||||
:type configurator: :class:`~nginx.configurator.NginxConfigurator`
|
||||
|
||||
:ivar list achalls: Annotated
|
||||
class:`~certbot.achallenges.KeyAuthorizationAnnotatedChallenge`
|
||||
challenges
|
||||
|
||||
:param list indices: Meant to hold indices of challenges in a
|
||||
larger array. NginxTlsSni01 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 http-01 Challenges,
|
||||
TLS-SNI-01 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 challenge on Nginx.
|
||||
|
||||
:returns: list of :class:`certbot.acme.challenges.TLSSNI01Response`
|
||||
:rtype: list
|
||||
|
||||
"""
|
||||
if not self.achalls:
|
||||
return []
|
||||
|
||||
self.configurator.save()
|
||||
|
||||
addresses = []
|
||||
default_addr = "{0} default_server ssl".format(
|
||||
self.configurator.config.tls_sni_01_port)
|
||||
|
||||
for achall in self.achalls:
|
||||
vhost = self.configurator.choose_vhost(achall.domain)
|
||||
if vhost is None:
|
||||
logger.error(
|
||||
"No nginx vhost exists with server_name matching: %s. "
|
||||
"Please specify server_names in the Nginx config.",
|
||||
achall.domain)
|
||||
return None
|
||||
|
||||
for addr in vhost.addrs:
|
||||
if addr.default:
|
||||
addresses.append([obj.Addr.fromstring(default_addr)])
|
||||
break
|
||||
else:
|
||||
addresses.append(list(vhost.addrs))
|
||||
|
||||
# Create challenge certs
|
||||
responses = [self._setup_challenge_cert(x) for x in self.achalls]
|
||||
|
||||
# Set up the configuration
|
||||
self._mod_config(addresses)
|
||||
|
||||
# Save reversible changes
|
||||
self.configurator.save("SNI Challenge", True)
|
||||
|
||||
return responses
|
||||
|
||||
def _mod_config(self, ll_addrs):
|
||||
"""Modifies Nginx config to include challenge server blocks.
|
||||
|
||||
:param list ll_addrs: list of lists of
|
||||
:class:`certbot_nginx.obj.Addr` to apply
|
||||
|
||||
:raises .MisconfigurationError:
|
||||
Unable to find a suitable HTTP block in which to include
|
||||
authenticator hosts.
|
||||
|
||||
"""
|
||||
# Add the 'include' statement for the challenges if it doesn't exist
|
||||
# already in the main config
|
||||
included = False
|
||||
include_directive = ['include', self.challenge_conf]
|
||||
root = self.configurator.parser.loc["root"]
|
||||
|
||||
bucket_directive = ['server_names_hash_bucket_size', '128']
|
||||
|
||||
main = self.configurator.parser.parsed[root]
|
||||
for key, body in main:
|
||||
if key == ['http']:
|
||||
found_bucket = False
|
||||
for k, _ in body:
|
||||
if k == bucket_directive[0]:
|
||||
found_bucket = True
|
||||
if not found_bucket:
|
||||
body.insert(0, bucket_directive)
|
||||
if include_directive not in body:
|
||||
body.insert(0, include_directive)
|
||||
included = True
|
||||
break
|
||||
if not included:
|
||||
raise errors.MisconfigurationError(
|
||||
'LetsEncrypt could not find an HTTP block to include '
|
||||
'TLS-SNI-01 challenges in %s.' % root)
|
||||
|
||||
config = [self._make_server_block(pair[0], pair[1])
|
||||
for pair in itertools.izip(self.achalls, ll_addrs)]
|
||||
|
||||
self.configurator.reverter.register_file_creation(
|
||||
True, self.challenge_conf)
|
||||
|
||||
with open(self.challenge_conf, "w") as new_conf:
|
||||
nginxparser.dump(config, new_conf)
|
||||
|
||||
def _make_server_block(self, achall, addrs):
|
||||
"""Creates a server block for a challenge.
|
||||
|
||||
:param achall: Annotated TLS-SNI-01 challenge
|
||||
:type achall:
|
||||
:class:`certbot.achallenges.KeyAuthorizationAnnotatedChallenge`
|
||||
|
||||
:param list addrs: addresses of challenged domain
|
||||
:class:`list` of type :class:`~nginx.obj.Addr`
|
||||
|
||||
:returns: server block for the challenge host
|
||||
:rtype: list
|
||||
|
||||
"""
|
||||
document_root = os.path.join(
|
||||
self.configurator.config.work_dir, "tls_sni_01_page")
|
||||
|
||||
block = [['listen', str(addr)] for addr in addrs]
|
||||
|
||||
block.extend([['server_name',
|
||||
achall.response(achall.account_key).z_domain],
|
||||
['include', self.configurator.parser.loc["ssl_options"]],
|
||||
# access and error logs necessary for
|
||||
# integration testing (non-root)
|
||||
['access_log', os.path.join(
|
||||
self.configurator.config.work_dir, 'access.log')],
|
||||
['error_log', os.path.join(
|
||||
self.configurator.config.work_dir, 'error.log')],
|
||||
['ssl_certificate', self.get_cert_path(achall)],
|
||||
['ssl_certificate_key', self.get_key_path(achall)],
|
||||
[['location', '/'], [['root', document_root]]]])
|
||||
|
||||
return [['server'], block]
|
||||
@@ -0,0 +1 @@
|
||||
/_build/
|
||||
@@ -0,0 +1,192 @@
|
||||
# Makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = sphinx-build
|
||||
PAPER =
|
||||
BUILDDIR = _build
|
||||
|
||||
# User-friendly check for sphinx-build
|
||||
ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
|
||||
$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
|
||||
endif
|
||||
|
||||
# Internal variables.
|
||||
PAPEROPT_a4 = -D latex_paper_size=a4
|
||||
PAPEROPT_letter = -D latex_paper_size=letter
|
||||
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||
# the i18n builder cannot share the environment and doctrees with the others
|
||||
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||
|
||||
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext
|
||||
|
||||
help:
|
||||
@echo "Please use \`make <target>' where <target> is one of"
|
||||
@echo " html to make standalone HTML files"
|
||||
@echo " dirhtml to make HTML files named index.html in directories"
|
||||
@echo " singlehtml to make a single large HTML file"
|
||||
@echo " pickle to make pickle files"
|
||||
@echo " json to make JSON files"
|
||||
@echo " htmlhelp to make HTML files and a HTML help project"
|
||||
@echo " qthelp to make HTML files and a qthelp project"
|
||||
@echo " applehelp to make an Apple Help Book"
|
||||
@echo " devhelp to make HTML files and a Devhelp project"
|
||||
@echo " epub to make an epub"
|
||||
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
||||
@echo " latexpdf to make LaTeX files and run them through pdflatex"
|
||||
@echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
|
||||
@echo " text to make text files"
|
||||
@echo " man to make manual pages"
|
||||
@echo " texinfo to make Texinfo files"
|
||||
@echo " info to make Texinfo files and run them through makeinfo"
|
||||
@echo " gettext to make PO message catalogs"
|
||||
@echo " changes to make an overview of all changed/added/deprecated items"
|
||||
@echo " xml to make Docutils-native XML files"
|
||||
@echo " pseudoxml to make pseudoxml-XML files for display purposes"
|
||||
@echo " linkcheck to check all external links for integrity"
|
||||
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
||||
@echo " coverage to run coverage check of the documentation (if enabled)"
|
||||
|
||||
clean:
|
||||
rm -rf $(BUILDDIR)/*
|
||||
|
||||
html:
|
||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||
|
||||
dirhtml:
|
||||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
||||
|
||||
singlehtml:
|
||||
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
|
||||
|
||||
pickle:
|
||||
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
||||
@echo
|
||||
@echo "Build finished; now you can process the pickle files."
|
||||
|
||||
json:
|
||||
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
||||
@echo
|
||||
@echo "Build finished; now you can process the JSON files."
|
||||
|
||||
htmlhelp:
|
||||
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
||||
".hhp project file in $(BUILDDIR)/htmlhelp."
|
||||
|
||||
qthelp:
|
||||
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
||||
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
|
||||
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/certbot-nginx.qhcp"
|
||||
@echo "To view the help file:"
|
||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/certbot-nginx.qhc"
|
||||
|
||||
applehelp:
|
||||
$(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp
|
||||
@echo
|
||||
@echo "Build finished. The help book is in $(BUILDDIR)/applehelp."
|
||||
@echo "N.B. You won't be able to view it unless you put it in" \
|
||||
"~/Library/Documentation/Help or install it in your application" \
|
||||
"bundle."
|
||||
|
||||
devhelp:
|
||||
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
|
||||
@echo
|
||||
@echo "Build finished."
|
||||
@echo "To view the help file:"
|
||||
@echo "# mkdir -p $$HOME/.local/share/devhelp/certbot-nginx"
|
||||
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/certbot-nginx"
|
||||
@echo "# devhelp"
|
||||
|
||||
epub:
|
||||
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
|
||||
@echo
|
||||
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
|
||||
|
||||
latex:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo
|
||||
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
||||
@echo "Run \`make' in that directory to run these through (pdf)latex" \
|
||||
"(use \`make latexpdf' here to do that automatically)."
|
||||
|
||||
latexpdf:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo "Running LaTeX files through pdflatex..."
|
||||
$(MAKE) -C $(BUILDDIR)/latex all-pdf
|
||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||
|
||||
latexpdfja:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo "Running LaTeX files through platex and dvipdfmx..."
|
||||
$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
|
||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||
|
||||
text:
|
||||
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
|
||||
@echo
|
||||
@echo "Build finished. The text files are in $(BUILDDIR)/text."
|
||||
|
||||
man:
|
||||
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
|
||||
@echo
|
||||
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
|
||||
|
||||
texinfo:
|
||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||
@echo
|
||||
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
|
||||
@echo "Run \`make' in that directory to run these through makeinfo" \
|
||||
"(use \`make info' here to do that automatically)."
|
||||
|
||||
info:
|
||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||
@echo "Running Texinfo files through makeinfo..."
|
||||
make -C $(BUILDDIR)/texinfo info
|
||||
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
|
||||
|
||||
gettext:
|
||||
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
|
||||
@echo
|
||||
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
|
||||
|
||||
changes:
|
||||
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
||||
@echo
|
||||
@echo "The overview file is in $(BUILDDIR)/changes."
|
||||
|
||||
linkcheck:
|
||||
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
|
||||
@echo
|
||||
@echo "Link check complete; look for any errors in the above output " \
|
||||
"or in $(BUILDDIR)/linkcheck/output.txt."
|
||||
|
||||
doctest:
|
||||
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
||||
@echo "Testing of doctests in the sources finished, look at the " \
|
||||
"results in $(BUILDDIR)/doctest/output.txt."
|
||||
|
||||
coverage:
|
||||
$(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage
|
||||
@echo "Testing of coverage in the sources finished, look at the " \
|
||||
"results in $(BUILDDIR)/coverage/python.txt."
|
||||
|
||||
xml:
|
||||
$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
|
||||
@echo
|
||||
@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
|
||||
|
||||
pseudoxml:
|
||||
$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
|
||||
@echo
|
||||
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
|
||||
@@ -0,0 +1,8 @@
|
||||
=================
|
||||
API Documentation
|
||||
=================
|
||||
|
||||
.. toctree::
|
||||
:glob:
|
||||
|
||||
api/**
|
||||
@@ -0,0 +1,5 @@
|
||||
:mod:`certbot_nginx.nginxparser`
|
||||
------------------------------------
|
||||
|
||||
.. automodule:: certbot_nginx.nginxparser
|
||||
:members:
|
||||
@@ -0,0 +1,5 @@
|
||||
:mod:`certbot_nginx.obj`
|
||||
----------------------------
|
||||
|
||||
.. automodule:: certbot_nginx.obj
|
||||
:members:
|
||||
@@ -0,0 +1,5 @@
|
||||
:mod:`certbot_nginx.parser`
|
||||
-------------------------------
|
||||
|
||||
.. automodule:: certbot_nginx.parser
|
||||
:members:
|
||||
@@ -0,0 +1,5 @@
|
||||
:mod:`certbot_nginx.tls_sni_01`
|
||||
-----------------------------------
|
||||
|
||||
.. automodule:: certbot_nginx.tls_sni_01
|
||||
:members:
|
||||
@@ -0,0 +1,311 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# certbot-nginx documentation build configuration file, created by
|
||||
# sphinx-quickstart on Sun Oct 18 13:39:39 2015.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its
|
||||
# containing dir.
|
||||
#
|
||||
# Note that not all possible configuration values are present in this
|
||||
# autogenerated file.
|
||||
#
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
import sys
|
||||
import os
|
||||
import shlex
|
||||
|
||||
|
||||
here = os.path.abspath(os.path.dirname(__file__))
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(here, '..')))
|
||||
|
||||
# -- General configuration ------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
needs_sphinx = '1.0'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = [
|
||||
'sphinx.ext.autodoc',
|
||||
'sphinx.ext.intersphinx',
|
||||
'sphinx.ext.todo',
|
||||
'sphinx.ext.coverage',
|
||||
'sphinx.ext.viewcode',
|
||||
]
|
||||
|
||||
autodoc_member_order = 'bysource'
|
||||
autodoc_default_flags = ['show-inheritance', 'private-members']
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# The suffix(es) of source filenames.
|
||||
# You can specify multiple suffix as a list of string:
|
||||
# source_suffix = ['.rst', '.md']
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The encoding of source files.
|
||||
#source_encoding = 'utf-8-sig'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'certbot-nginx'
|
||||
copyright = u'2014-2015, Let\'s Encrypt Project'
|
||||
author = u'Let\'s Encrypt Project'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '0'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '0'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#
|
||||
# This is also used if you do content translation via gettext catalogs.
|
||||
# Usually you set "language" from the command line for these cases.
|
||||
language = 'en'
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
#today = ''
|
||||
# Else, today_fmt is used as the format for a strftime call.
|
||||
#today_fmt = '%B %d, %Y'
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
exclude_patterns = ['_build']
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all
|
||||
# documents.
|
||||
default_role = 'py:obj'
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
#add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
#add_module_names = True
|
||||
|
||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||
# output. They are ignored by default.
|
||||
#show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
#modindex_common_prefix = []
|
||||
|
||||
# If true, keep warnings as "system message" paragraphs in the built documents.
|
||||
#keep_warnings = False
|
||||
|
||||
# If true, `todo` and `todoList` produce output, else they produce nothing.
|
||||
todo_include_todos = True
|
||||
|
||||
|
||||
# -- Options for HTML output ----------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
|
||||
# http://docs.readthedocs.org/en/latest/theme.html#how-do-i-use-this-locally-and-on-read-the-docs
|
||||
# on_rtd is whether we are on readthedocs.org
|
||||
on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
|
||||
if not on_rtd: # only import and set the theme if we're building docs locally
|
||||
import sphinx_rtd_theme
|
||||
html_theme = 'sphinx_rtd_theme'
|
||||
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
|
||||
# otherwise, readthedocs.org uses their theme by default, so no need to specify it
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
#html_theme_path = []
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
#html_title = None
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
#html_short_title = None
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top
|
||||
# of the sidebar.
|
||||
#html_logo = None
|
||||
|
||||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
#html_favicon = None
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
|
||||
# Add any extra paths that contain custom files (such as robots.txt or
|
||||
# .htaccess) here, relative to this directory. These files are copied
|
||||
# directly to the root of the documentation.
|
||||
#html_extra_path = []
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
#html_last_updated_fmt = '%b %d, %Y'
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
#html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
#html_sidebars = {}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
#html_additional_pages = {}
|
||||
|
||||
# If false, no module index is generated.
|
||||
#html_domain_indices = True
|
||||
|
||||
# If false, no index is generated.
|
||||
#html_use_index = True
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
#html_split_index = False
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
#html_show_sourcelink = True
|
||||
|
||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||
#html_show_sphinx = True
|
||||
|
||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||
#html_show_copyright = True
|
||||
|
||||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
#html_use_opensearch = ''
|
||||
|
||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
#html_file_suffix = None
|
||||
|
||||
# Language to be used for generating the HTML full-text search index.
|
||||
# Sphinx supports the following languages:
|
||||
# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja'
|
||||
# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr'
|
||||
#html_search_language = 'en'
|
||||
|
||||
# A dictionary with options for the search language support, empty by default.
|
||||
# Now only 'ja' uses this config value
|
||||
#html_search_options = {'type': 'default'}
|
||||
|
||||
# The name of a javascript file (relative to the configuration directory) that
|
||||
# implements a search results scorer. If empty, the default will be used.
|
||||
#html_search_scorer = 'scorer.js'
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'certbot-nginxdoc'
|
||||
|
||||
# -- Options for LaTeX output ---------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#'papersize': 'letterpaper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#'pointsize': '10pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#'preamble': '',
|
||||
|
||||
# Latex figure (float) alignment
|
||||
#'figure_align': 'htbp',
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title,
|
||||
# author, documentclass [howto, manual, or own class]).
|
||||
latex_documents = [
|
||||
(master_doc, 'certbot-nginx.tex', u'certbot-nginx Documentation',
|
||||
u'Let\'s Encrypt Project', 'manual'),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
# the title page.
|
||||
#latex_logo = None
|
||||
|
||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||
# not chapters.
|
||||
#latex_use_parts = False
|
||||
|
||||
# If true, show page references after internal links.
|
||||
#latex_show_pagerefs = False
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#latex_show_urls = False
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#latex_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#latex_domain_indices = True
|
||||
|
||||
|
||||
# -- Options for manual page output ---------------------------------------
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
(master_doc, 'certbot-nginx', u'certbot-nginx Documentation',
|
||||
[author], 1)
|
||||
]
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#man_show_urls = False
|
||||
|
||||
|
||||
# -- Options for Texinfo output -------------------------------------------
|
||||
|
||||
# Grouping the document tree into Texinfo files. List of tuples
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
(master_doc, 'certbot-nginx', u'certbot-nginx Documentation',
|
||||
author, 'certbot-nginx', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#texinfo_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#texinfo_domain_indices = True
|
||||
|
||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
||||
#texinfo_show_urls = 'footnote'
|
||||
|
||||
# If true, do not generate a @detailmenu in the "Top" node's menu.
|
||||
#texinfo_no_detailmenu = False
|
||||
|
||||
|
||||
intersphinx_mapping = {
|
||||
'python': ('https://docs.python.org/', None),
|
||||
'acme': ('https://acme-python.readthedocs.org/en/latest/', None),
|
||||
'certbot': ('https://letsencrypt.readthedocs.org/en/latest/', None),
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
.. certbot-nginx documentation master file, created by
|
||||
sphinx-quickstart on Sun Oct 18 13:39:39 2015.
|
||||
You can adapt this file completely to your liking, but it should at least
|
||||
contain the root `toctree` directive.
|
||||
|
||||
Welcome to certbot-nginx's documentation!
|
||||
=============================================
|
||||
|
||||
Contents:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
api
|
||||
|
||||
|
||||
.. automodule:: certbot_nginx
|
||||
:members:
|
||||
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
||||
|
||||
@@ -0,0 +1,263 @@
|
||||
@ECHO OFF
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
set SPHINXBUILD=sphinx-build
|
||||
)
|
||||
set BUILDDIR=_build
|
||||
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
|
||||
set I18NSPHINXOPTS=%SPHINXOPTS% .
|
||||
if NOT "%PAPER%" == "" (
|
||||
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
|
||||
set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
|
||||
)
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
if "%1" == "help" (
|
||||
:help
|
||||
echo.Please use `make ^<target^>` where ^<target^> is one of
|
||||
echo. html to make standalone HTML files
|
||||
echo. dirhtml to make HTML files named index.html in directories
|
||||
echo. singlehtml to make a single large HTML file
|
||||
echo. pickle to make pickle files
|
||||
echo. json to make JSON files
|
||||
echo. htmlhelp to make HTML files and a HTML help project
|
||||
echo. qthelp to make HTML files and a qthelp project
|
||||
echo. devhelp to make HTML files and a Devhelp project
|
||||
echo. epub to make an epub
|
||||
echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
|
||||
echo. text to make text files
|
||||
echo. man to make manual pages
|
||||
echo. texinfo to make Texinfo files
|
||||
echo. gettext to make PO message catalogs
|
||||
echo. changes to make an overview over all changed/added/deprecated items
|
||||
echo. xml to make Docutils-native XML files
|
||||
echo. pseudoxml to make pseudoxml-XML files for display purposes
|
||||
echo. linkcheck to check all external links for integrity
|
||||
echo. doctest to run all doctests embedded in the documentation if enabled
|
||||
echo. coverage to run coverage check of the documentation if enabled
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "clean" (
|
||||
for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
|
||||
del /q /s %BUILDDIR%\*
|
||||
goto end
|
||||
)
|
||||
|
||||
|
||||
REM Check if sphinx-build is available and fallback to Python version if any
|
||||
%SPHINXBUILD% 2> nul
|
||||
if errorlevel 9009 goto sphinx_python
|
||||
goto sphinx_ok
|
||||
|
||||
:sphinx_python
|
||||
|
||||
set SPHINXBUILD=python -m sphinx.__init__
|
||||
%SPHINXBUILD% 2> nul
|
||||
if errorlevel 9009 (
|
||||
echo.
|
||||
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
|
||||
echo.installed, then set the SPHINXBUILD environment variable to point
|
||||
echo.to the full path of the 'sphinx-build' executable. Alternatively you
|
||||
echo.may add the Sphinx directory to PATH.
|
||||
echo.
|
||||
echo.If you don't have Sphinx installed, grab it from
|
||||
echo.http://sphinx-doc.org/
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
:sphinx_ok
|
||||
|
||||
|
||||
if "%1" == "html" (
|
||||
%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/html.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "dirhtml" (
|
||||
%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "singlehtml" (
|
||||
%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "pickle" (
|
||||
%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can process the pickle files.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "json" (
|
||||
%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can process the JSON files.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "htmlhelp" (
|
||||
%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can run HTML Help Workshop with the ^
|
||||
.hhp project file in %BUILDDIR%/htmlhelp.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "qthelp" (
|
||||
%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can run "qcollectiongenerator" with the ^
|
||||
.qhcp project file in %BUILDDIR%/qthelp, like this:
|
||||
echo.^> qcollectiongenerator %BUILDDIR%\qthelp\certbot-nginx.qhcp
|
||||
echo.To view the help file:
|
||||
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\certbot-nginx.ghc
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "devhelp" (
|
||||
%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "epub" (
|
||||
%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The epub file is in %BUILDDIR%/epub.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "latex" (
|
||||
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "latexpdf" (
|
||||
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
|
||||
cd %BUILDDIR%/latex
|
||||
make all-pdf
|
||||
cd %~dp0
|
||||
echo.
|
||||
echo.Build finished; the PDF files are in %BUILDDIR%/latex.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "latexpdfja" (
|
||||
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
|
||||
cd %BUILDDIR%/latex
|
||||
make all-pdf-ja
|
||||
cd %~dp0
|
||||
echo.
|
||||
echo.Build finished; the PDF files are in %BUILDDIR%/latex.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "text" (
|
||||
%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The text files are in %BUILDDIR%/text.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "man" (
|
||||
%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The manual pages are in %BUILDDIR%/man.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "texinfo" (
|
||||
%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "gettext" (
|
||||
%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "changes" (
|
||||
%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.The overview file is in %BUILDDIR%/changes.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "linkcheck" (
|
||||
%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Link check complete; look for any errors in the above output ^
|
||||
or in %BUILDDIR%/linkcheck/output.txt.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "doctest" (
|
||||
%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Testing of doctests in the sources finished, look at the ^
|
||||
results in %BUILDDIR%/doctest/output.txt.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "coverage" (
|
||||
%SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Testing of coverage in the sources finished, look at the ^
|
||||
results in %BUILDDIR%/coverage/python.txt.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "xml" (
|
||||
%SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The XML files are in %BUILDDIR%/xml.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "pseudoxml" (
|
||||
%SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml.
|
||||
goto end
|
||||
)
|
||||
|
||||
:end
|
||||
@@ -0,0 +1,12 @@
|
||||
# readthedocs.org gives no way to change the install command to "pip
|
||||
# install -e .[docs]" (that would in turn install documentation
|
||||
# dependencies), but it allows to specify a requirements.txt file at
|
||||
# https://readthedocs.org/dashboard/letsencrypt/advanced/ (c.f. #259)
|
||||
|
||||
# Although ReadTheDocs certainly doesn't need to install the project
|
||||
# in --editable mode (-e), just "pip install .[docs]" does not work as
|
||||
# expected and "pip install -e .[docs]" must be used instead
|
||||
|
||||
-e acme
|
||||
-e .
|
||||
-e certbot-nginx[docs]
|
||||
@@ -0,0 +1,69 @@
|
||||
import sys
|
||||
|
||||
from setuptools import setup
|
||||
from setuptools import find_packages
|
||||
|
||||
|
||||
version = '0.6.0.dev0'
|
||||
|
||||
# Please update tox.ini when modifying dependency version requirements
|
||||
install_requires = [
|
||||
'acme=={0}'.format(version),
|
||||
'certbot=={0}'.format(version),
|
||||
'PyOpenSSL',
|
||||
'pyparsing>=1.5.5', # Python3 support; perhaps unnecessary?
|
||||
# For pkg_resources. >=1.0 so pip resolves it to a version cryptography
|
||||
# will tolerate; see #2599:
|
||||
'setuptools>=1.0',
|
||||
'zope.interface',
|
||||
]
|
||||
|
||||
if sys.version_info < (2, 7):
|
||||
install_requires.append('mock<1.1.0')
|
||||
else:
|
||||
install_requires.append('mock')
|
||||
|
||||
docs_extras = [
|
||||
'Sphinx>=1.0', # autodoc_member_order = 'bysource', autodoc_default_flags
|
||||
'sphinx_rtd_theme',
|
||||
]
|
||||
|
||||
setup(
|
||||
name='certbot-nginx',
|
||||
version=version,
|
||||
description="Nginx plugin for Let's Encrypt client",
|
||||
url='https://github.com/letsencrypt/letsencrypt',
|
||||
author="Let's Encrypt Project",
|
||||
author_email='client-dev@letsencrypt.org',
|
||||
license='Apache License 2.0',
|
||||
classifiers=[
|
||||
'Development Status :: 3 - Alpha',
|
||||
'Environment :: Plugins',
|
||||
'Intended Audience :: System Administrators',
|
||||
'License :: OSI Approved :: Apache Software License',
|
||||
'Operating System :: POSIX :: Linux',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 2',
|
||||
'Programming Language :: Python :: 2.6',
|
||||
'Programming Language :: Python :: 2.7',
|
||||
'Topic :: Internet :: WWW/HTTP',
|
||||
'Topic :: Security',
|
||||
'Topic :: System :: Installation/Setup',
|
||||
'Topic :: System :: Networking',
|
||||
'Topic :: System :: Systems Administration',
|
||||
'Topic :: Utilities',
|
||||
],
|
||||
|
||||
packages=find_packages(),
|
||||
include_package_data=True,
|
||||
install_requires=install_requires,
|
||||
extras_require={
|
||||
'docs': docs_extras,
|
||||
},
|
||||
entry_points={
|
||||
'certbot.plugins': [
|
||||
'nginx = certbot_nginx.configurator:NginxConfigurator',
|
||||
],
|
||||
},
|
||||
test_suite='certbot_nginx',
|
||||
)
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
# Based on
|
||||
# https://www.exratione.com/2014/03/running-nginx-as-a-non-root-user/
|
||||
# https://github.com/exratione/non-root-nginx/blob/9a77f62e5d5cb9c9026fd62eece76b9514011019/nginx.conf
|
||||
|
||||
cat <<EOF
|
||||
# This error log will be written regardless of server scope error_log
|
||||
# definitions, so we have to set this here in the main scope.
|
||||
#
|
||||
# Even doing this, Nginx will still try to create the default error file, and
|
||||
# log a non-fatal error when it fails. After that things will work, however.
|
||||
error_log $root/error.log;
|
||||
|
||||
# The pidfile will be written to /var/run unless this is set.
|
||||
pid $root/nginx.pid;
|
||||
|
||||
worker_processes 1;
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
# Set an array of temp, cache and log file options that will otherwise default to
|
||||
# restricted locations accessible only to root.
|
||||
client_body_temp_path $root/client_body;
|
||||
fastcgi_temp_path $root/fastcgi_temp;
|
||||
proxy_temp_path $root/proxy_temp;
|
||||
#scgi_temp_path $root/scgi_temp;
|
||||
#uwsgi_temp_path $root/uwsgi_temp;
|
||||
access_log $root/error.log;
|
||||
|
||||
# This should be turned off in a Virtualbox VM, as it can cause some
|
||||
# interesting issues with data corruption in delivered files.
|
||||
sendfile off;
|
||||
|
||||
tcp_nopush on;
|
||||
tcp_nodelay on;
|
||||
keepalive_timeout 65;
|
||||
types_hash_max_size 2048;
|
||||
|
||||
#include /etc/nginx/mime.types;
|
||||
index index.html index.htm index.php;
|
||||
|
||||
log_format main '\$remote_addr - \$remote_user [\$time_local] \$status '
|
||||
'"\$request" \$body_bytes_sent "\$http_referer" '
|
||||
'"\$http_user_agent" "\$http_x_forwarded_for"';
|
||||
|
||||
default_type application/octet-stream;
|
||||
|
||||
server {
|
||||
# IPv4.
|
||||
listen 8081;
|
||||
# IPv6.
|
||||
listen [::]:8081 default ipv6only=on;
|
||||
|
||||
root $root/webroot;
|
||||
|
||||
location / {
|
||||
# First attempt to serve request as file, then as directory, then fall
|
||||
# back to index.html.
|
||||
try_files \$uri \$uri/ /index.html;
|
||||
}
|
||||
}
|
||||
}
|
||||
EOF
|
||||
Executable
+28
@@ -0,0 +1,28 @@
|
||||
#!/bin/sh -xe
|
||||
# prerequisite: apt-get install --no-install-recommends nginx-light openssl
|
||||
|
||||
. ./tests/integration/_common.sh
|
||||
|
||||
export PATH="/usr/sbin:$PATH" # /usr/sbin/nginx
|
||||
nginx_root="$root/nginx"
|
||||
mkdir $nginx_root
|
||||
root="$nginx_root" ./certbot-nginx/tests/boulder-integration.conf.sh > $nginx_root/nginx.conf
|
||||
|
||||
killall nginx || true
|
||||
nginx -c $nginx_root/nginx.conf
|
||||
|
||||
certbot_test_nginx () {
|
||||
certbot_test \
|
||||
--configurator nginx \
|
||||
--nginx-server-root $nginx_root \
|
||||
"$@"
|
||||
}
|
||||
|
||||
certbot_test_nginx --domains nginx.wtf run
|
||||
echo | openssl s_client -connect localhost:5001 \
|
||||
| openssl x509 -out $root/nginx.pem
|
||||
diff -q $root/nginx.pem $root/conf/live/nginx.wtf/cert.pem
|
||||
|
||||
# note: not reached if anything above fails, hence "killall" at the
|
||||
# top
|
||||
nginx -c $nginx_root/nginx.conf -s stop
|
||||
Reference in New Issue
Block a user