mirror of
https://github.com/certbot/certbot.git
synced 2026-05-28 04:34:11 -04:00
Merge branch 'master' into issue_2983
This commit is contained in:
commit
2884a7fcb7
42 changed files with 1202 additions and 414 deletions
3
.coveragerc
Normal file
3
.coveragerc
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
[report]
|
||||
# show lines missing coverage in output
|
||||
show_missing = True
|
||||
10
.gitattributes
vendored
10
.gitattributes
vendored
|
|
@ -1,7 +1,15 @@
|
|||
* text=auto eol=lf
|
||||
#Default, normalize CRLF into LF in non-binary files
|
||||
# Files identified as binary by Git are not changed
|
||||
* crlf=auto
|
||||
|
||||
# special files
|
||||
*.sh crlf=input
|
||||
*.py crlf=input
|
||||
|
||||
*.bat text eol=crlf
|
||||
|
||||
*.der binary
|
||||
*.gz binary
|
||||
*.jpeg binary
|
||||
*.jpg binary
|
||||
*.png binary
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ class MissingNonce(NonceError):
|
|||
|
||||
def __str__(self):
|
||||
return ('Server {0} response did not include a replay '
|
||||
'nonce, headers: {1}'.format(
|
||||
'nonce, headers: {1} (This may be a service outage)'.format(
|
||||
self.response.request.method, self.response.headers))
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -52,11 +52,14 @@ let sep_eq = del /[ \t]*=[ \t]*/ "="
|
|||
let nmtoken = /[a-zA-Z:_][a-zA-Z0-9:_.-]*/
|
||||
let word = /[a-z][a-z0-9._-]*/i
|
||||
|
||||
let comment = Util.comment
|
||||
let eol = Util.doseol
|
||||
let empty = Util.empty_dos
|
||||
let indent = Util.indent
|
||||
|
||||
let comment_val_re = /([^ \t\r\n](.|\\\\\r?\n)*[^ \\\t\r\n]|[^ \t\r\n])/
|
||||
let comment = [ label "#comment" . del /[ \t]*#[ \t]*/ "# "
|
||||
. store comment_val_re . eol ]
|
||||
|
||||
(* borrowed from shellvars.aug *)
|
||||
let char_arg_dir = /([^\\ '"{\t\r\n]|[^ '"{\t\r\n]+[^\\ \t\r\n])|\\\\"|\\\\'|\\\\ /
|
||||
let char_arg_sec = /([^\\ '"\t\r\n>]|[^ '"\t\r\n>]+[^\\ \t\r\n>])|\\\\"|\\\\'|\\\\ /
|
||||
|
|
|
|||
|
|
@ -631,50 +631,93 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
|||
|
||||
"""
|
||||
|
||||
# If nonstandard port, add service definition for matching
|
||||
if port != "443":
|
||||
port_service = "%s %s" % (port, "https")
|
||||
else:
|
||||
port_service = port
|
||||
|
||||
self.prepare_https_modules(temp)
|
||||
# Check for Listen <port>
|
||||
# Note: This could be made to also look for ip:443 combo
|
||||
listens = [self.parser.get_arg(x).split()[0] for
|
||||
x in self.parser.find_dir("Listen")]
|
||||
|
||||
# In case no Listens are set (which really is a broken apache config)
|
||||
if not listens:
|
||||
listens = ["80"]
|
||||
if port in listens:
|
||||
|
||||
# Listen already in place
|
||||
if self._has_port_already(listens, port):
|
||||
return
|
||||
|
||||
listen_dirs = set(listens)
|
||||
|
||||
for listen in listens:
|
||||
# For any listen statement, check if the machine also listens on
|
||||
# Port 443. If not, add such a listen statement.
|
||||
if len(listen.split(":")) == 1:
|
||||
# Its listening to all interfaces
|
||||
if port not in listens:
|
||||
if port == "443":
|
||||
args = [port]
|
||||
else:
|
||||
# Non-standard ports should specify https protocol
|
||||
args = [port, "https"]
|
||||
self.parser.add_dir_to_ifmodssl(
|
||||
parser.get_aug_path(
|
||||
self.parser.loc["listen"]), "Listen", args)
|
||||
self.save_notes += "Added Listen %s directive to %s\n" % (
|
||||
port, self.parser.loc["listen"])
|
||||
listens.append(port)
|
||||
if port not in listen_dirs and port_service not in listen_dirs:
|
||||
listen_dirs.add(port_service)
|
||||
else:
|
||||
# The Listen statement specifies an ip
|
||||
_, ip = listen[::-1].split(":", 1)
|
||||
ip = ip[::-1]
|
||||
if "%s:%s" % (ip, port) not in listens:
|
||||
if port == "443":
|
||||
args = ["%s:%s" % (ip, port)]
|
||||
else:
|
||||
# Non-standard ports should specify https protocol
|
||||
args = ["%s:%s" % (ip, port), "https"]
|
||||
self.parser.add_dir_to_ifmodssl(
|
||||
parser.get_aug_path(
|
||||
self.parser.loc["listen"]), "Listen", args)
|
||||
self.save_notes += ("Added Listen %s:%s directive to "
|
||||
"%s\n") % (ip, port,
|
||||
self.parser.loc["listen"])
|
||||
listens.append("%s:%s" % (ip, port))
|
||||
if "%s:%s" % (ip, port_service) not in listen_dirs and (
|
||||
"%s:%s" % (ip, port_service) not in listen_dirs):
|
||||
listen_dirs.add("%s:%s" % (ip, port_service))
|
||||
self._add_listens(listen_dirs, listens, port)
|
||||
|
||||
def _add_listens(self, listens, listens_orig, port):
|
||||
"""Helper method for prepare_server_https to figure out which new
|
||||
listen statements need adding
|
||||
|
||||
:param set listens: Set of all needed Listen statements
|
||||
:param list listens_orig: List of existing listen statements
|
||||
:param string port: Port number we're adding
|
||||
"""
|
||||
|
||||
# Add service definition for non-standard ports
|
||||
if port != "443":
|
||||
port_service = "%s %s" % (port, "https")
|
||||
else:
|
||||
port_service = port
|
||||
|
||||
new_listens = listens.difference(listens_orig)
|
||||
|
||||
if port in new_listens or port_service in new_listens:
|
||||
# We have wildcard, skip the rest
|
||||
self.parser.add_dir_to_ifmodssl(
|
||||
parser.get_aug_path(self.parser.loc["listen"]),
|
||||
"Listen", port_service.split(" "))
|
||||
self.save_notes += "Added Listen %s directive to %s\n" % (
|
||||
port_service, self.parser.loc["listen"])
|
||||
else:
|
||||
for listen in new_listens:
|
||||
self.parser.add_dir_to_ifmodssl(
|
||||
parser.get_aug_path(self.parser.loc["listen"]),
|
||||
"Listen", listen.split(" "))
|
||||
self.save_notes += ("Added Listen %s directive to "
|
||||
"%s\n") % (listen,
|
||||
self.parser.loc["listen"])
|
||||
|
||||
def _has_port_already(self, listens, port):
|
||||
"""Helper method for prepare_server_https to find out if user
|
||||
already has an active Listen statement for the port we need
|
||||
|
||||
:param list listens: List of listen variables
|
||||
:param string port: Port in question
|
||||
"""
|
||||
|
||||
if port in listens:
|
||||
return True
|
||||
# Check if Apache is already listening on a specific IP
|
||||
for listen in listens:
|
||||
if len(listen.split(":")) > 1:
|
||||
# Ugly but takes care of protocol def, eg: 1.1.1.1:443 https
|
||||
if listen.split(":")[-1].split(" ")[0] == port:
|
||||
return True
|
||||
|
||||
def prepare_https_modules(self, temp):
|
||||
"""Helper method for prepare_server_https, taking care of enabling
|
||||
|
|
|
|||
|
|
@ -0,0 +1,428 @@
|
|||
# ---------------------------------------------------------------
|
||||
# Core ModSecurity Rule Set ver.2.2.6
|
||||
# Copyright (C) 2006-2012 Trustwave All rights reserved.
|
||||
#
|
||||
# The OWASP ModSecurity Core Rule Set is distributed under
|
||||
# Apache Software License (ASL) version 2
|
||||
# Please see the enclosed LICENCE file for full details.
|
||||
# ---------------------------------------------------------------
|
||||
|
||||
|
||||
#
|
||||
# -- [[ Recommended Base Configuration ]] -------------------------------------------------
|
||||
#
|
||||
# The configuration directives/settings in this file are used to control
|
||||
# the OWASP ModSecurity CRS. These settings do **NOT** configure the main
|
||||
# ModSecurity settings such as:
|
||||
#
|
||||
# - SecRuleEngine
|
||||
# - SecRequestBodyAccess
|
||||
# - SecAuditEngine
|
||||
# - SecDebugLog
|
||||
#
|
||||
# You should use the modsecurity.conf-recommended file that comes with the
|
||||
# ModSecurity source code archive.
|
||||
#
|
||||
# Ref: http://mod-security.svn.sourceforge.net/viewvc/mod-security/m2/trunk/modsecurity.conf-recommended
|
||||
#
|
||||
|
||||
|
||||
#
|
||||
# -- [[ Rule Version ]] -------------------------------------------------------------------
|
||||
#
|
||||
# Rule version data is added to the "Producer" line of Section H of the Audit log:
|
||||
#
|
||||
# - Producer: ModSecurity for Apache/2.7.0-rc1 (http://www.modsecurity.org/); OWASP_CRS/2.2.4.
|
||||
#
|
||||
# Ref: https://sourceforge.net/apps/mediawiki/mod-security/index.php?title=Reference_Manual#SecComponentSignature
|
||||
#
|
||||
#SecComponentSignature "OWASP_CRS/2.2.6"
|
||||
|
||||
|
||||
#
|
||||
# -- [[ Modes of Operation: Self-Contained vs. Collaborative Detection ]] -----------------
|
||||
#
|
||||
# Each detection rule uses the "block" action which will inherit the SecDefaultAction
|
||||
# specified below. Your settings here will determine which mode of operation you use.
|
||||
#
|
||||
# -- [[ Self-Contained Mode ]] --
|
||||
# Rules inherit the "deny" disruptive action. The first rule that matches will block.
|
||||
#
|
||||
# -- [[ Collaborative Detection Mode ]] --
|
||||
# This is a "delayed blocking" mode of operation where each matching rule will inherit
|
||||
# the "pass" action and will only contribute to anomaly scores. Transactional blocking
|
||||
# can be applied
|
||||
#
|
||||
# -- [[ Alert Logging Control ]] --
|
||||
# You have three options -
|
||||
#
|
||||
# - To log to both the Apache error_log and ModSecurity audit_log file use: "log"
|
||||
# - To log *only* to the ModSecurity audit_log file use: "nolog,auditlog"
|
||||
# - To log *only* to the Apache error_log file use: "log,noauditlog"
|
||||
#
|
||||
# Ref: http://blog.spiderlabs.com/2010/11/advanced-topic-of-the-week-traditional-vs-anomaly-scoring-detection-modes.html
|
||||
# Ref: https://sourceforge.net/apps/mediawiki/mod-security/index.php?title=Reference_Manual#SecDefaultAction
|
||||
#
|
||||
#SecDefaultAction "phase:1,deny,log"
|
||||
|
||||
|
||||
#
|
||||
# -- [[ Collaborative Detection Severity Levels ]] ----------------------------------------
|
||||
#
|
||||
# These are the default scoring points for each severity level. You may
|
||||
# adjust these to you liking. These settings will be used in macro expansion
|
||||
# in the rules to increment the anomaly scores when rules match.
|
||||
#
|
||||
# These are the default Severity ratings (with anomaly scores) of the individual rules -
|
||||
#
|
||||
# - 2: Critical - Anomaly Score of 5.
|
||||
# Is the highest severity level possible without correlation. It is
|
||||
# normally generated by the web attack rules (40 level files).
|
||||
# - 3: Error - Anomaly Score of 4.
|
||||
# Is generated mostly from outbound leakage rules (50 level files).
|
||||
# - 4: Warning - Anomaly Score of 3.
|
||||
# Is generated by malicious client rules (35 level files).
|
||||
# - 5: Notice - Anomaly Score of 2.
|
||||
# Is generated by the Protocol policy and anomaly files.
|
||||
#
|
||||
#SecAction \
|
||||
"id:'900001', \
|
||||
phase:1, \
|
||||
t:none, \
|
||||
setvar:tx.critical_anomaly_score=5, \
|
||||
setvar:tx.error_anomaly_score=4, \
|
||||
setvar:tx.warning_anomaly_score=3, \
|
||||
setvar:tx.notice_anomaly_score=2, \
|
||||
nolog, \
|
||||
pass"
|
||||
|
||||
|
||||
#
|
||||
# -- [[ Collaborative Detection Scoring Threshold Levels ]] ------------------------------
|
||||
#
|
||||
# These variables are used in macro expansion in the 49 inbound blocking and 59
|
||||
# outbound blocking files.
|
||||
#
|
||||
# **MUST HAVE** ModSecurity v2.5.12 or higher to use macro expansion in numeric
|
||||
# operators. If you have an earlier version, edit the 49/59 files directly to
|
||||
# set the appropriate anomaly score levels.
|
||||
#
|
||||
# You should set the score to the proper threshold you would prefer. If set to "5"
|
||||
# it will work similarly to previous Mod CRS rules and will create an event in the error_log
|
||||
# file if there are any rules that match. If you would like to lessen the number of events
|
||||
# generated in the error_log file, you should increase the anomaly score threshold to
|
||||
# something like "20". This would only generate an event in the error_log file if
|
||||
# there are multiple lower severity rule matches or if any 1 higher severity item matches.
|
||||
#
|
||||
#SecAction \
|
||||
"id:'900002', \
|
||||
phase:1, \
|
||||
t:none, \
|
||||
setvar:tx.inbound_anomaly_score_level=5, \
|
||||
nolog, \
|
||||
pass"
|
||||
|
||||
|
||||
#SecAction \
|
||||
"id:'900003', \
|
||||
phase:1, \
|
||||
t:none, \
|
||||
setvar:tx.outbound_anomaly_score_level=4, \
|
||||
nolog, \
|
||||
pass"
|
||||
|
||||
|
||||
#
|
||||
# -- [[ Collaborative Detection Blocking ]] -----------------------------------------------
|
||||
#
|
||||
# This is a collaborative detection mode where each rule will increment an overall
|
||||
# anomaly score for the transaction. The scores are then evaluated in the following files:
|
||||
#
|
||||
# Inbound anomaly score - checked in the modsecurity_crs_49_inbound_blocking.conf file
|
||||
# Outbound anomaly score - checked in the modsecurity_crs_59_outbound_blocking.conf file
|
||||
#
|
||||
# If you want to use anomaly scoring mode, then uncomment this line.
|
||||
#
|
||||
#SecAction \
|
||||
"id:'900004', \
|
||||
phase:1, \
|
||||
t:none, \
|
||||
setvar:tx.anomaly_score_blocking=on, \
|
||||
nolog, \
|
||||
pass"
|
||||
|
||||
|
||||
#
|
||||
# -- [[ GeoIP Database ]] -----------------------------------------------------------------
|
||||
#
|
||||
# There are some rulesets that need to inspect the GEO data of the REMOTE_ADDR data.
|
||||
#
|
||||
# You must first download the MaxMind GeoIP Lite City DB -
|
||||
#
|
||||
# http://geolite.maxmind.com/download/geoip/database/GeoLiteCity.dat.gz
|
||||
#
|
||||
# You then need to define the proper path for the SecGeoLookupDb directive
|
||||
#
|
||||
# Ref: http://blog.spiderlabs.com/2010/10/detecting-malice-with-modsecurity-geolocation-data.html
|
||||
# Ref: http://blog.spiderlabs.com/2010/11/detecting-malice-with-modsecurity-ip-forensics.html
|
||||
#
|
||||
#SecGeoLookupDb /opt/modsecurity/lib/GeoLiteCity.dat
|
||||
|
||||
#
|
||||
# -- [[ Regression Testing Mode ]] --------------------------------------------------------
|
||||
#
|
||||
# If you are going to run the regression testing mode, you should uncomment the
|
||||
# following rule. It will enable DetectionOnly mode for the SecRuleEngine and
|
||||
# will enable Response Header tagging so that the client testing script can see
|
||||
# which rule IDs have matched.
|
||||
#
|
||||
# You must specify the your source IP address where you will be running the tests
|
||||
# from.
|
||||
#
|
||||
#SecRule REMOTE_ADDR "@ipMatch 192.168.1.100" \
|
||||
"id:'900005', \
|
||||
phase:1, \
|
||||
t:none, \
|
||||
ctl:ruleEngine=DetectionOnly, \
|
||||
setvar:tx.regression_testing=1, \
|
||||
nolog, \
|
||||
pass"
|
||||
|
||||
|
||||
#
|
||||
# -- [[ HTTP Policy Settings ]] ----------------------------------------------------------
|
||||
#
|
||||
# Set the following policy settings here and they will be propagated to the 23 rules
|
||||
# file (modsecurity_common_23_request_limits.conf) by using macro expansion.
|
||||
# If you run into false positives, you can adjust the settings here.
|
||||
#
|
||||
# Only the max number of args is uncommented by default as there are a high rate
|
||||
# of false positives. Uncomment the items you wish to set.
|
||||
#
|
||||
#
|
||||
# -- Maximum number of arguments in request limited
|
||||
#SecAction \
|
||||
"id:'900006', \
|
||||
phase:1, \
|
||||
t:none, \
|
||||
setvar:tx.max_num_args=255, \
|
||||
nolog, \
|
||||
pass"
|
||||
|
||||
#
|
||||
# -- Limit argument name length
|
||||
#SecAction \
|
||||
"id:'900007', \
|
||||
phase:1, \
|
||||
t:none, \
|
||||
setvar:tx.arg_name_length=100, \
|
||||
nolog, \
|
||||
pass"
|
||||
|
||||
#
|
||||
# -- Limit value name length
|
||||
#SecAction \
|
||||
"id:'900008', \
|
||||
phase:1, \
|
||||
t:none, \
|
||||
setvar:tx.arg_length=400, \
|
||||
nolog, \
|
||||
pass"
|
||||
|
||||
#
|
||||
# -- Limit arguments total length
|
||||
#SecAction \
|
||||
"id:'900009', \
|
||||
phase:1, \
|
||||
t:none, \
|
||||
setvar:tx.total_arg_length=64000, \
|
||||
nolog, \
|
||||
pass"
|
||||
|
||||
#
|
||||
# -- Individual file size is limited
|
||||
#SecAction \
|
||||
"id:'900010', \
|
||||
phase:1, \
|
||||
t:none, \
|
||||
setvar:tx.max_file_size=1048576, \
|
||||
nolog, \
|
||||
pass"
|
||||
|
||||
#
|
||||
# -- Combined file size is limited
|
||||
#SecAction \
|
||||
"id:'900011', \
|
||||
phase:1, \
|
||||
t:none, \
|
||||
setvar:tx.combined_file_sizes=1048576, \
|
||||
nolog, \
|
||||
pass"
|
||||
|
||||
|
||||
#
|
||||
# Set the following policy settings here and they will be propagated to the 30 rules
|
||||
# file (modsecurity_crs_30_http_policy.conf) by using macro expansion.
|
||||
# If you run into false positves, you can adjust the settings here.
|
||||
#
|
||||
#SecAction \
|
||||
"id:'900012', \
|
||||
phase:1, \
|
||||
t:none, \
|
||||
setvar:'tx.allowed_methods=GET HEAD POST OPTIONS', \
|
||||
setvar:'tx.allowed_request_content_type=application/x-www-form-urlencoded|multipart/form-data|text/xml|application/xml|application/x-amf', \
|
||||
setvar:'tx.allowed_http_versions=HTTP/0.9 HTTP/1.0 HTTP/1.1', \
|
||||
setvar:'tx.restricted_extensions=.asa/ .asax/ .ascx/ .axd/ .backup/ .bak/ .bat/ .cdx/ .cer/ .cfg/ .cmd/ .com/ .config/ .conf/ .cs/ .csproj/ .csr/ .dat/ .db/ .dbf/ .dll/ .dos/ .htr/ .htw/ .ida/ .idc/ .idq/ .inc/ .ini/ .key/ .licx/ .lnk/ .log/ .mdb/ .old/ .pass/ .pdb/ .pol/ .printer/ .pwd/ .resources/ .resx/ .sql/ .sys/ .vb/ .vbs/ .vbproj/ .vsdisco/ .webinfo/ .xsd/ .xsx/', \
|
||||
setvar:'tx.restricted_headers=/Proxy-Connection/ /Lock-Token/ /Content-Range/ /Translate/ /via/ /if/', \
|
||||
nolog, \
|
||||
pass"
|
||||
|
||||
|
||||
#
|
||||
# -- [[ Content Security Policy (CSP) Settings ]] -----------------------------------------
|
||||
#
|
||||
# The purpose of these settings is to send CSP response headers to
|
||||
# Mozilla FireFox users so that you can enforce how dynamic content
|
||||
# is used. CSP usage helps to prevent XSS attacks against your users.
|
||||
#
|
||||
# Reference Link:
|
||||
#
|
||||
# https://developer.mozilla.org/en/Security/CSP
|
||||
#
|
||||
# Uncomment this SecAction line if you want use CSP enforcement.
|
||||
# You need to set the appropriate directives and settings for your site/domain and
|
||||
# and activate the CSP file in the experimental_rules directory.
|
||||
#
|
||||
# Ref: http://blog.spiderlabs.com/2011/04/modsecurity-advanced-topic-of-the-week-integrating-content-security-policy-csp.html
|
||||
#
|
||||
#SecAction \
|
||||
"id:'900013', \
|
||||
phase:1, \
|
||||
t:none, \
|
||||
setvar:tx.csp_report_only=1, \
|
||||
setvar:tx.csp_report_uri=/csp_violation_report, \
|
||||
setenv:'csp_policy=allow \'self\'; img-src *.yoursite.com; media-src *.yoursite.com; style-src *.yoursite.com; frame-ancestors *.yoursite.com; script-src *.yoursite.com; report-uri %{tx.csp_report_uri}', \
|
||||
nolog, \
|
||||
pass"
|
||||
|
||||
|
||||
#
|
||||
# -- [[ Brute Force Protection ]] ---------------------------------------------------------
|
||||
#
|
||||
# If you are using the Brute Force Protection rule set, then uncomment the following
|
||||
# lines and set the following variables:
|
||||
# - Protected URLs: resources to protect (e.g. login pages) - set to your login page
|
||||
# - Burst Time Slice Interval: time interval window to monitor for bursts
|
||||
# - Request Threshold: request # threshold to trigger a burst
|
||||
# - Block Period: temporary block timeout
|
||||
#
|
||||
#SecAction \
|
||||
"id:'900014', \
|
||||
phase:1, \
|
||||
t:none, \
|
||||
setvar:'tx.brute_force_protected_urls=/login.jsp /partner_login.php', \
|
||||
setvar:'tx.brute_force_burst_time_slice=60', \
|
||||
setvar:'tx.brute_force_counter_threshold=10', \
|
||||
setvar:'tx.brute_force_block_timeout=300', \
|
||||
nolog, \
|
||||
pass"
|
||||
|
||||
|
||||
#
|
||||
# -- [[ DoS Protection ]] ----------------------------------------------------------------
|
||||
#
|
||||
# If you are using the DoS Protection rule set, then uncomment the following
|
||||
# lines and set the following variables:
|
||||
# - Burst Time Slice Interval: time interval window to monitor for bursts
|
||||
# - Request Threshold: request # threshold to trigger a burst
|
||||
# - Block Period: temporary block timeout
|
||||
#
|
||||
#SecAction \
|
||||
"id:'900015', \
|
||||
phase:1, \
|
||||
t:none, \
|
||||
setvar:'tx.dos_burst_time_slice=60', \
|
||||
setvar:'tx.dos_counter_threshold=100', \
|
||||
setvar:'tx.dos_block_timeout=600', \
|
||||
nolog, \
|
||||
pass"
|
||||
|
||||
|
||||
#
|
||||
# -- [[ Check UTF enconding ]] -----------------------------------------------------------
|
||||
#
|
||||
# We only want to apply this check if UTF-8 encoding is actually used by the site, otherwise
|
||||
# it will result in false positives.
|
||||
#
|
||||
# Uncomment this line if your site uses UTF8 encoding
|
||||
#SecAction \
|
||||
"id:'900016', \
|
||||
phase:1, \
|
||||
t:none, \
|
||||
setvar:tx.crs_validate_utf8_encoding=1, \
|
||||
nolog, \
|
||||
pass"
|
||||
|
||||
|
||||
#
|
||||
# -- [[ Enable XML Body Parsing ]] -------------------------------------------------------
|
||||
#
|
||||
# The rules in this file will trigger the XML parser upon an XML request
|
||||
#
|
||||
# Initiate XML Processor in case of xml content-type
|
||||
#
|
||||
#SecRule REQUEST_HEADERS:Content-Type "text/xml" \
|
||||
"id:'900017', \
|
||||
phase:1, \
|
||||
t:none,t:lowercase, \
|
||||
nolog, \
|
||||
pass, \
|
||||
chain"
|
||||
#SecRule REQBODY_PROCESSOR "!@streq XML" \
|
||||
"ctl:requestBodyProcessor=XML"
|
||||
|
||||
|
||||
#
|
||||
# -- [[ Global and IP Collections ]] -----------------------------------------------------
|
||||
#
|
||||
# Create both Global and IP collections for rules to use
|
||||
# There are some CRS rules that assume that these two collections
|
||||
# have already been initiated.
|
||||
#
|
||||
#SecRule REQUEST_HEADERS:User-Agent "^(.*)$" \
|
||||
"id:'900018', \
|
||||
phase:1, \
|
||||
t:none,t:sha1,t:hexEncode, \
|
||||
setvar:tx.ua_hash=%{matched_var}, \
|
||||
nolog, \
|
||||
pass"
|
||||
|
||||
|
||||
#SecRule REQUEST_HEADERS:x-forwarded-for "^\b(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\b" \
|
||||
"id:'900019', \
|
||||
phase:1, \
|
||||
t:none, \
|
||||
capture, \
|
||||
setvar:tx.real_ip=%{tx.1}, \
|
||||
nolog, \
|
||||
pass"
|
||||
|
||||
|
||||
#SecRule &TX:REAL_IP "!@eq 0" \
|
||||
"id:'900020', \
|
||||
phase:1, \
|
||||
t:none, \
|
||||
initcol:global=global, \
|
||||
initcol:ip=%{tx.real_ip}_%{tx.ua_hash}, \
|
||||
nolog, \
|
||||
pass"
|
||||
|
||||
|
||||
#SecRule &TX:REAL_IP "@eq 0" \
|
||||
"id:'900021', \
|
||||
phase:1, \
|
||||
t:none, \
|
||||
initcol:global=global, \
|
||||
initcol:ip=%{remote_addr}_%{tx.ua_hash}, \
|
||||
nolog, \
|
||||
pass"
|
||||
|
|
@ -497,13 +497,8 @@ class MultipleVhostsTest(util.ApacheTest):
|
|||
|
||||
# Test Listen statements with specific ip listeed
|
||||
self.config.prepare_server_https("443")
|
||||
# Should only be 2 here, as the third interface
|
||||
# already listens to the correct port
|
||||
self.assertEqual(mock_add_dir.call_count, 2)
|
||||
|
||||
# Check argument to new Listen statements
|
||||
self.assertEqual(mock_add_dir.call_args_list[0][0][2], ["1.2.3.4:443"])
|
||||
self.assertEqual(mock_add_dir.call_args_list[1][0][2], ["[::1]:443"])
|
||||
# Should be 0 as one interface already listens to 443
|
||||
self.assertEqual(mock_add_dir.call_count, 0)
|
||||
|
||||
# Reset return lists and inputs
|
||||
mock_add_dir.reset_mock()
|
||||
|
|
@ -519,6 +514,28 @@ class MultipleVhostsTest(util.ApacheTest):
|
|||
self.assertEqual(mock_add_dir.call_args_list[2][0][2],
|
||||
["1.1.1.1:8080", "https"])
|
||||
|
||||
# mock_get.side_effect = ["1.2.3.4:80", "[::1]:80"]
|
||||
# mock_find.return_value = ["test1", "test2", "test3"]
|
||||
# self.config.parser.get_arg = mock_get
|
||||
# self.config.prepare_server_https("8080", temp=True)
|
||||
# self.assertEqual(self.listens, 0)
|
||||
|
||||
def test_prepare_server_https_needed_listen(self):
|
||||
mock_find = mock.Mock()
|
||||
mock_find.return_value = ["test1", "test2"]
|
||||
mock_get = mock.Mock()
|
||||
mock_get.side_effect = ["1.2.3.4:8080", "80"]
|
||||
mock_add_dir = mock.Mock()
|
||||
mock_enable = mock.Mock()
|
||||
|
||||
self.config.parser.find_dir = mock_find
|
||||
self.config.parser.get_arg = mock_get
|
||||
self.config.parser.add_dir_to_ifmodssl = mock_add_dir
|
||||
self.config.enable_mod = mock_enable
|
||||
|
||||
self.config.prepare_server_https("443")
|
||||
self.assertEqual(mock_add_dir.call_count, 1)
|
||||
|
||||
def test_prepare_server_https_mixed_listen(self):
|
||||
|
||||
mock_find = mock.Mock()
|
||||
|
|
|
|||
52
certbot-auto
52
certbot-auto
|
|
@ -19,7 +19,7 @@ XDG_DATA_HOME=${XDG_DATA_HOME:-~/.local/share}
|
|||
VENV_NAME="letsencrypt"
|
||||
VENV_PATH=${VENV_PATH:-"$XDG_DATA_HOME/$VENV_NAME"}
|
||||
VENV_BIN="$VENV_PATH/bin"
|
||||
LE_AUTO_VERSION="0.8.0"
|
||||
LE_AUTO_VERSION="0.8.1"
|
||||
BASENAME=$(basename $0)
|
||||
USAGE="Usage: $BASENAME [OPTIONS]
|
||||
A self-updating wrapper script for the Certbot ACME client. When run, updates
|
||||
|
|
@ -172,7 +172,7 @@ BootstrapDebCommon() {
|
|||
# distro version (#346)
|
||||
|
||||
virtualenv=
|
||||
if apt-cache show virtualenv > /dev/null 2>&1; then
|
||||
if apt-cache show virtualenv > /dev/null 2>&1 && ! apt-cache --quiet=0 show virtualenv 2>&1 | grep -q 'No packages found'; then
|
||||
virtualenv="virtualenv"
|
||||
fi
|
||||
|
||||
|
|
@ -458,12 +458,39 @@ BootstrapSmartOS() {
|
|||
pkgin -y install 'gcc49' 'py27-augeas' 'py27-virtualenv'
|
||||
}
|
||||
|
||||
BootstrapMageiaCommon() {
|
||||
if ! $SUDO urpmi --force \
|
||||
python \
|
||||
libpython-devel \
|
||||
python-virtualenv
|
||||
then
|
||||
echo "Could not install Python dependencies. Aborting bootstrap!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! $SUDO urpmi --force \
|
||||
git \
|
||||
gcc \
|
||||
cdialog \
|
||||
python-augeas \
|
||||
libopenssl-devel \
|
||||
libffi-devel \
|
||||
rootcerts
|
||||
then
|
||||
echo "Could not install additional dependencies. Aborting bootstrap!"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
# Install required OS packages:
|
||||
Bootstrap() {
|
||||
if [ -f /etc/debian_version ]; then
|
||||
echo "Bootstrapping dependencies for Debian-based OSes..."
|
||||
BootstrapDebCommon
|
||||
elif [ -f /etc/mageia-release ] ; then
|
||||
# Mageia has both /etc/mageia-release and /etc/redhat-release
|
||||
ExperimentalBootstrap "Mageia" BootstrapMageiaCommon
|
||||
elif [ -f /etc/redhat-release ]; then
|
||||
echo "Bootstrapping dependencies for RedHat-based OSes..."
|
||||
BootstrapRpmCommon
|
||||
|
|
@ -476,7 +503,7 @@ Bootstrap() {
|
|||
BootstrapArchCommon
|
||||
else
|
||||
echo "Please use pacman to install letsencrypt packages:"
|
||||
echo "# pacman -S letsencrypt letsencrypt-apache"
|
||||
echo "# pacman -S certbot certbot-apache"
|
||||
echo
|
||||
echo "If you would like to use the virtualenv way, please run the script again with the"
|
||||
echo "--debug flag."
|
||||
|
|
@ -500,6 +527,7 @@ Bootstrap() {
|
|||
echo "You will need to bootstrap, configure virtualenv, and run pip install manually."
|
||||
echo "Please see https://letsencrypt.readthedocs.org/en/latest/contributing.html#prerequisites"
|
||||
echo "for more info."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
|
|
@ -719,15 +747,15 @@ letsencrypt==0.7.0 \
|
|||
|
||||
# THE LINES BELOW ARE EDITED BY THE RELEASE SCRIPT; ADD ALL DEPENDENCIES ABOVE.
|
||||
|
||||
acme==0.8.0 \
|
||||
--hash=sha256:8561d590e496afb41a8ff2dac389199661d9cd785b1636ae08325771511189af \
|
||||
--hash=sha256:dfa86b547628b231f275c7e0efc7a09bec5dfaec866f89f5c5b59b78c14564da
|
||||
certbot==0.8.0 \
|
||||
--hash=sha256:395c5840ff6b75aa51ee6449c86d016c14c5f65a71281e7bcef5feecac6a3293 \
|
||||
--hash=sha256:3c3c70b484fb3243a166515adc81ae0401c5d687a2763c75b40df9d8241a4314
|
||||
certbot-apache==0.8.0 \
|
||||
--hash=sha256:f4d4fc962ecc19646f6745d49c62a265d26e5b2df3acf34ef4865351594156e3 \
|
||||
--hash=sha256:cfb211debbcb0d0645c88d7e8bb38c591fca263bfdb5337242c023956055e268
|
||||
acme==0.8.1 \
|
||||
--hash=sha256:ccd7883772efbf933f91713b8241455993834f3620c8fbd459d9ed5e50bbaaca \
|
||||
--hash=sha256:d3ea4acf280bf6253ad7d641cb0970f230a19805acfed809e7a8ddcf62157d9f
|
||||
certbot==0.8.1 \
|
||||
--hash=sha256:89805d9f70249ae859ec4d7a99c00b4bb7083ca90cd12d4d202b76dfc284f7c5 \
|
||||
--hash=sha256:6ca8df3d310ced6687d38aac17c0fb8c1b2ec7a3bea156a254e4cc2a1c132771
|
||||
certbot-apache==0.8.1 \
|
||||
--hash=sha256:c9e3fdc15e65589c2e39eb0e6b1f61f0c0a1db3c17b00bb337f0ff636cc61cb3 \
|
||||
--hash=sha256:0faf2879884d3b7a58b071902fba37d4b8b58a50e2c3b8ac262c0a74134045ed
|
||||
|
||||
UNLIKELY_EOF
|
||||
# -------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -152,17 +152,17 @@ class NginxConfigurator(common.Plugin):
|
|||
"install a cert.")
|
||||
|
||||
vhost = self.choose_vhost(domain)
|
||||
cert_directives = [['ssl_certificate', fullchain_path],
|
||||
['ssl_certificate_key', key_path]]
|
||||
cert_directives = [['\n', 'ssl_certificate', ' ', fullchain_path],
|
||||
['\n', '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']]
|
||||
['\n', 'ssl_trusted_certificate', ' ', chain_path],
|
||||
['\n', 'ssl_stapling', ' ', 'on'],
|
||||
['\n', 'ssl_stapling_verify', ' ', 'on'], ['\n']]
|
||||
|
||||
if len(stapling_directives) != 0 and not chain_path:
|
||||
raise errors.PluginError(
|
||||
|
|
@ -225,7 +225,7 @@ class NginxConfigurator(common.Plugin):
|
|||
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]]]
|
||||
new_block = [['server'], [['\n', '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]))
|
||||
|
|
@ -337,10 +337,10 @@ class NginxConfigurator(common.Plugin):
|
|||
|
||||
"""
|
||||
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"]]]
|
||||
ssl_block = [['\n', 'listen', ' ', '{0} ssl'.format(self.config.tls_sni_01_port)],
|
||||
['\n', 'ssl_certificate', ' ', snakeoil_cert],
|
||||
['\n', 'ssl_certificate_key', ' ', snakeoil_key],
|
||||
['\n', 'include', ' ', self.parser.loc["ssl_options"]]]
|
||||
self.parser.add_server_directives(
|
||||
vhost.filep, vhost.names, ssl_block, replace=False)
|
||||
vhost.ssl = True
|
||||
|
|
@ -689,11 +689,6 @@ def nginx_restart(nginx_ctl, nginx_conf="/etc/nginx.conf"):
|
|||
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -1,23 +1,30 @@
|
|||
"""Very low-level nginx config parser based on pyparsing."""
|
||||
# Forked from https://github.com/fatiherikli/nginxparser (MIT Licensed)
|
||||
import copy
|
||||
import logging
|
||||
import string
|
||||
|
||||
from pyparsing import (
|
||||
Literal, White, Word, alphanums, CharsNotIn, Forward, Group,
|
||||
Literal, White, Word, alphanums, CharsNotIn, Combine, Forward, Group,
|
||||
Optional, OneOrMore, Regex, ZeroOrMore)
|
||||
from pyparsing import stringEnd
|
||||
from pyparsing import restOfLine
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class RawNginxParser(object):
|
||||
# pylint: disable=expression-not-assigned
|
||||
"""A class that parses nginx configuration with pyparsing."""
|
||||
|
||||
# constants
|
||||
space = Optional(White())
|
||||
nonspace = Regex(r"\S+")
|
||||
left_bracket = Literal("{").suppress()
|
||||
right_bracket = Literal("}").suppress()
|
||||
right_bracket = space.leaveWhitespace() + Literal("}").suppress()
|
||||
semicolon = Literal(";").suppress()
|
||||
space = White().suppress()
|
||||
key = Word(alphanums + "_/+-.")
|
||||
dollar_var = Combine(Literal('$') + nonspace)
|
||||
condition = Regex(r"\(.+\)")
|
||||
# Matches anything that is not a special character AND any chars in single
|
||||
# or double quotes
|
||||
value = Regex(r"((\".*\")?(\'.*\')?[^\{\};,]?)+")
|
||||
|
|
@ -26,20 +33,25 @@ class RawNginxParser(object):
|
|||
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
|
||||
map_statement = Literal("map") + space + Regex(r"\S+") + space + Regex(r"\$\S+") + space
|
||||
comment = space + Literal('#') + restOfLine()
|
||||
|
||||
assignment = space + key + Optional(space + value, default=None) + semicolon
|
||||
location_statement = space + Optional(modifier) + Optional(space + location + space)
|
||||
if_statement = space + Literal("if") + space + condition + space
|
||||
map_statement = space + Literal("map") + space + nonspace + space + dollar_var + space
|
||||
block = Forward()
|
||||
|
||||
block << Group(
|
||||
(Group(key + location_statement) ^ Group(if_statement) ^ Group(map_statement)) +
|
||||
# key could for instance be "server" or "http", or "location" (in which case
|
||||
# location_statement needs to have a non-empty location)
|
||||
(Group(space + key + location_statement) ^ Group(if_statement) ^
|
||||
Group(map_statement)).leaveWhitespace() +
|
||||
left_bracket +
|
||||
Group(ZeroOrMore(Group(comment | assignment) | block)) +
|
||||
Group(ZeroOrMore(Group(comment | assignment) | block) + space).leaveWhitespace() +
|
||||
right_bracket)
|
||||
|
||||
script = OneOrMore(Group(comment | assignment) ^ block) + stringEnd
|
||||
script = OneOrMore(Group(comment | assignment) ^ block) + space + stringEnd
|
||||
script.parseWithTabs()
|
||||
|
||||
def __init__(self, source):
|
||||
self.source = source
|
||||
|
|
@ -52,42 +64,47 @@ class RawNginxParser(object):
|
|||
"""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):
|
||||
def __init__(self, blocks):
|
||||
self.blocks = blocks
|
||||
self.indentation = indentation
|
||||
|
||||
def __iter__(self, blocks=None, current_indent=0, spacer=' '):
|
||||
def __iter__(self, blocks=None):
|
||||
"""Iterates the dumped nginx content."""
|
||||
blocks = blocks or self.blocks
|
||||
for key, values in blocks:
|
||||
indentation = spacer * current_indent
|
||||
for b0 in blocks:
|
||||
if isinstance(b0, str):
|
||||
yield b0
|
||||
continue
|
||||
b = copy.deepcopy(b0)
|
||||
if spacey(b[0]):
|
||||
yield b.pop(0) # indentation
|
||||
if not b:
|
||||
continue
|
||||
key, values = b.pop(0), b.pop(0)
|
||||
|
||||
if isinstance(key, list):
|
||||
if current_indent:
|
||||
yield ''
|
||||
yield indentation + spacer.join(key) + ' {'
|
||||
|
||||
yield "".join(key) + '{'
|
||||
for parameter in values:
|
||||
dumped = self.__iter__([parameter], current_indent + self.indentation)
|
||||
for line in dumped:
|
||||
for line in self.__iter__([parameter]): # negate "for b0 in blocks"
|
||||
yield line
|
||||
|
||||
yield indentation + '}'
|
||||
yield '}'
|
||||
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 + ';'
|
||||
if isinstance(key, str) and key.strip() == '#': # comment
|
||||
yield key + values
|
||||
else: # assignment
|
||||
gap = ""
|
||||
# Sometimes the parser has stuck some gap whitespace in here;
|
||||
# if so rotate it into gap
|
||||
if values and spacey(values):
|
||||
gap = values
|
||||
values = b.pop(0)
|
||||
yield key + gap + values + ';'
|
||||
|
||||
def __str__(self):
|
||||
"""Return the parsed block as a string."""
|
||||
return '\n'.join(self) + '\n'
|
||||
return ''.join(self)
|
||||
|
||||
|
||||
# Shortcut functions to respect Python's serialization interface
|
||||
|
|
@ -101,7 +118,7 @@ def loads(source):
|
|||
:rtype: list
|
||||
|
||||
"""
|
||||
return RawNginxParser(source).as_list()
|
||||
return UnspacedList(RawNginxParser(source).as_list())
|
||||
|
||||
|
||||
def load(_file):
|
||||
|
|
@ -115,24 +132,143 @@ def load(_file):
|
|||
return loads(_file.read())
|
||||
|
||||
|
||||
def dumps(blocks, indentation=4):
|
||||
def dumps(blocks):
|
||||
"""Dump to a string.
|
||||
|
||||
:param list block: The parsed tree
|
||||
:param UnspacedList block: The parsed tree
|
||||
:param int indentation: The number of spaces to indent
|
||||
:rtype: str
|
||||
|
||||
"""
|
||||
return str(RawNginxDumper(blocks, indentation))
|
||||
return str(RawNginxDumper(blocks.spaced))
|
||||
|
||||
|
||||
def dump(blocks, _file, indentation=4):
|
||||
def dump(blocks, _file):
|
||||
"""Dump to a file.
|
||||
|
||||
:param list block: The parsed tree
|
||||
:param UnspacedList 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))
|
||||
return _file.write(dumps(blocks))
|
||||
|
||||
|
||||
spacey = lambda x: (isinstance(x, str) and x.isspace()) or x == ''
|
||||
|
||||
class UnspacedList(list):
|
||||
"""Wrap a list [of lists], making any whitespace entries magically invisible"""
|
||||
|
||||
def __init__(self, list_source):
|
||||
# ensure our argument is not a generator, and duplicate any sublists
|
||||
self.spaced = copy.deepcopy(list(list_source))
|
||||
self.dirty = False
|
||||
|
||||
# Turn self into a version of the source list that has spaces removed
|
||||
# and all sub-lists also UnspacedList()ed
|
||||
list.__init__(self, list_source)
|
||||
for i, entry in reversed(list(enumerate(self))):
|
||||
if isinstance(entry, list):
|
||||
sublist = UnspacedList(entry)
|
||||
list.__setitem__(self, i, sublist)
|
||||
self.spaced[i] = sublist.spaced
|
||||
elif spacey(entry):
|
||||
# don't delete comments
|
||||
if "#" not in self[:i]:
|
||||
list.__delitem__(self, i)
|
||||
|
||||
def _coerce(self, inbound):
|
||||
"""
|
||||
Coerce some inbound object to be appropriately usable in this object
|
||||
|
||||
:param inbound: string or None or list or UnspacedList
|
||||
:returns: (coerced UnspacedList or string or None, spaced equivalent)
|
||||
:rtype: tuple
|
||||
|
||||
"""
|
||||
if not isinstance(inbound, list): # str or None
|
||||
return (inbound, inbound)
|
||||
else:
|
||||
if not hasattr(inbound, "spaced"):
|
||||
inbound = UnspacedList(inbound)
|
||||
return (inbound, inbound.spaced)
|
||||
|
||||
|
||||
def insert(self, i, x):
|
||||
item, spaced_item = self._coerce(x)
|
||||
self.spaced.insert(self._spaced_position(i), spaced_item)
|
||||
list.insert(self, i, item)
|
||||
self.dirty = True
|
||||
|
||||
def append(self, x):
|
||||
item, spaced_item = self._coerce(x)
|
||||
self.spaced.append(spaced_item)
|
||||
list.append(self, item)
|
||||
self.dirty = True
|
||||
|
||||
def extend(self, x):
|
||||
item, spaced_item = self._coerce(x)
|
||||
self.spaced.extend(spaced_item)
|
||||
list.extend(self, item)
|
||||
self.dirty = True
|
||||
|
||||
def __add__(self, other):
|
||||
l = copy.deepcopy(self)
|
||||
l.extend(other)
|
||||
l.dirty = True
|
||||
return l
|
||||
|
||||
def pop(self, _i=None):
|
||||
raise NotImplementedError("UnspacedList.pop() not yet implemented")
|
||||
def remove(self, _):
|
||||
raise NotImplementedError("UnspacedList.remove() not yet implemented")
|
||||
def reverse(self):
|
||||
raise NotImplementedError("UnspacedList.reverse() not yet implemented")
|
||||
def sort(self, _cmp=None, _key=None, _Rev=None):
|
||||
raise NotImplementedError("UnspacedList.sort() not yet implemented")
|
||||
def __setslice__(self, _i, _j, _newslice):
|
||||
raise NotImplementedError("Slice operations on UnspacedLists not yet implemented")
|
||||
|
||||
def __setitem__(self, i, value):
|
||||
if isinstance(i, slice):
|
||||
raise NotImplementedError("Slice operations on UnspacedLists not yet implemented")
|
||||
item, spaced_item = self._coerce(value)
|
||||
self.spaced.__setitem__(self._spaced_position(i), spaced_item)
|
||||
list.__setitem__(self, i, item)
|
||||
self.dirty = True
|
||||
|
||||
def __delitem__(self, i):
|
||||
self.spaced.__delitem__(self._spaced_position(i))
|
||||
list.__delitem__(self, i)
|
||||
self.dirty = True
|
||||
|
||||
def __deepcopy__(self, memo):
|
||||
l = UnspacedList(self[:])
|
||||
l.spaced = copy.deepcopy(self.spaced, memo=memo)
|
||||
l.dirty = self.dirty
|
||||
return l
|
||||
|
||||
def is_dirty(self):
|
||||
"""Recurse through the parse tree to figure out if any sublists are dirty"""
|
||||
if self.dirty:
|
||||
return True
|
||||
return any((isinstance(x, list) and x.is_dirty() for x in self))
|
||||
|
||||
def _spaced_position(self, idx):
|
||||
"Convert from indexes in the unspaced list to positions in the spaced one"
|
||||
pos = spaces = 0
|
||||
# Normalize indexes like list[-1] etc, and save the result
|
||||
if idx < 0:
|
||||
idx = len(self) + idx
|
||||
if not 0 <= idx < len(self):
|
||||
raise IndexError("list index out of range")
|
||||
idx0 = idx
|
||||
# Count the number of spaces in the spaced list before idx in the unspaced one
|
||||
while idx != -1:
|
||||
if spacey(self.spaced[pos]):
|
||||
spaces += 1
|
||||
else:
|
||||
idx -= 1
|
||||
pos += 1
|
||||
return idx0 + spaces
|
||||
|
|
|
|||
|
|
@ -85,6 +85,9 @@ class Addr(common.Addr):
|
|||
|
||||
return parts
|
||||
|
||||
def __repr__(self):
|
||||
return "Addr(" + self.__str__() + ")"
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, self.__class__):
|
||||
return (self.tup == other.tup and
|
||||
|
|
@ -126,6 +129,9 @@ class VirtualHost(object): # pylint: disable=too-few-public-methods
|
|||
"enabled: %s" % (self.filep, addr_str,
|
||||
self.names, self.ssl, self.enabled))
|
||||
|
||||
def __repr__(self):
|
||||
return "VirtualHost(" + self.__str__().replace("\n", ", ") + ")\n"
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, self.__class__):
|
||||
return (self.filep == other.filep and
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
"""NginxParser is a member object of the NginxConfigurator class."""
|
||||
import copy
|
||||
import glob
|
||||
import logging
|
||||
import os
|
||||
|
|
@ -17,7 +18,7 @@ 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
|
||||
:ivar str root: Normalized absolute path to the server root
|
||||
directory. Without trailing slash.
|
||||
:ivar dict parsed: Mapping of file paths to parsed trees
|
||||
|
||||
|
|
@ -113,6 +114,7 @@ class NginxParser(object):
|
|||
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'],
|
||||
|
|
@ -132,7 +134,7 @@ class NginxParser(object):
|
|||
:rtype: list
|
||||
|
||||
"""
|
||||
result = list(block) # Copy the list to keep self.parsed idempotent
|
||||
result = copy.deepcopy(block) # Copy the list to keep self.parsed idempotent
|
||||
for directive in block:
|
||||
if _is_include_directive(directive):
|
||||
included_files = glob.glob(
|
||||
|
|
@ -153,7 +155,9 @@ class NginxParser(object):
|
|||
:rtype: list
|
||||
|
||||
"""
|
||||
files = glob.glob(filepath)
|
||||
files = glob.glob(filepath) # nginx on unix calls glob(3) for this
|
||||
# XXX Windows nginx uses FindFirstFile, and
|
||||
# should have a narrower call here
|
||||
trees = []
|
||||
for item in files:
|
||||
if item in self.parsed and not override:
|
||||
|
|
@ -201,21 +205,27 @@ class NginxParser(object):
|
|||
raise errors.NoInstallationError(
|
||||
"Could not find configuration root")
|
||||
|
||||
def filedump(self, ext='tmp'):
|
||||
def filedump(self, ext='tmp', lazy=True):
|
||||
"""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.
|
||||
:param bool lazy: Only write files that have been modified
|
||||
|
||||
"""
|
||||
# Best-effort atomicity is enforced above us by reverter.py
|
||||
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))
|
||||
if lazy and not tree.is_dirty():
|
||||
continue
|
||||
out = nginxparser.dumps(tree)
|
||||
logger.debug('Writing nginx conf tree to %s:\n%s', filename, out)
|
||||
with open(filename, 'w') as _file:
|
||||
nginxparser.dump(tree, _file)
|
||||
_file.write(out)
|
||||
|
||||
except IOError:
|
||||
logger.error("Could not open file for writing: %s", filename)
|
||||
|
||||
|
|
@ -463,6 +473,8 @@ def parse_server(server):
|
|||
'names': set()}
|
||||
|
||||
for directive in server:
|
||||
if not directive:
|
||||
continue
|
||||
if directive[0] == 'listen':
|
||||
addr = obj.Addr.fromstring(directive[1])
|
||||
parsed_server['addrs'].add(addr)
|
||||
|
|
@ -503,6 +515,11 @@ def _add_directive(block, directive, replace):
|
|||
See _add_directives for more documentation.
|
||||
|
||||
"""
|
||||
directive = nginxparser.UnspacedList(directive)
|
||||
if len(directive) == 0:
|
||||
# whitespace
|
||||
block.append(directive)
|
||||
return
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ class NginxConfiguratorTest(util.NginxTest):
|
|||
filep = self.config.parser.abs_path('sites-enabled/example.com')
|
||||
self.config.parser.add_server_directives(
|
||||
filep, set(['.example.com', 'example.*']),
|
||||
[['listen', '5001 ssl']],
|
||||
[['listen', ' ', '5001 ssl']],
|
||||
replace=False)
|
||||
self.config.save()
|
||||
|
||||
|
|
@ -265,7 +265,8 @@ class NginxConfiguratorTest(util.NginxTest):
|
|||
|
||||
@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):
|
||||
@mock.patch("certbot_nginx.configurator.NginxConfigurator.revert_challenge_config")
|
||||
def test_perform_and_cleanup(self, mock_revert, 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(
|
||||
|
|
@ -291,7 +292,11 @@ class NginxConfiguratorTest(util.NginxTest):
|
|||
|
||||
self.assertEqual(mock_perform.call_count, 1)
|
||||
self.assertEqual(responses, expected)
|
||||
self.assertEqual(mock_restart.call_count, 1)
|
||||
|
||||
self.config.cleanup([achall1, achall2])
|
||||
self.assertEqual(0, self.config._chall_out) # pylint: disable=protected-access
|
||||
self.assertEqual(mock_revert.call_count, 1)
|
||||
self.assertEqual(mock_restart.call_count, 2)
|
||||
|
||||
@mock.patch("certbot_nginx.configurator.subprocess.Popen")
|
||||
def test_get_version(self, mock_popen):
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
"""Test for certbot_nginx.nginxparser."""
|
||||
import copy
|
||||
import operator
|
||||
import os
|
||||
import unittest
|
||||
|
||||
from pyparsing import ParseException
|
||||
|
||||
from certbot_nginx.nginxparser import (
|
||||
RawNginxParser, loads, load, dumps, dump)
|
||||
RawNginxParser, loads, load, dumps, dump, UnspacedList)
|
||||
from certbot_nginx.tests import util
|
||||
|
||||
|
||||
|
|
@ -17,39 +19,39 @@ class TestRawNginxParser(unittest.TestCase):
|
|||
|
||||
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'])
|
||||
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'], []]])
|
||||
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']]]])
|
||||
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(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']]],
|
||||
dumped = dumps(UnspacedList([
|
||||
['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']]],
|
||||
]]
|
||||
]]])
|
||||
]]]))
|
||||
|
||||
self.assertEqual(dumped,
|
||||
self.assertEqual(dumped.split('\n'),
|
||||
'user www-data;\n'
|
||||
'server {\n'
|
||||
' listen 80;\n'
|
||||
|
|
@ -60,10 +62,7 @@ class TestRawNginxParser(unittest.TestCase):
|
|||
' check_status;\n'
|
||||
'\n'
|
||||
' types {\n'
|
||||
' image/jpeg jpg;\n'
|
||||
' }\n'
|
||||
' }\n'
|
||||
'}\n')
|
||||
' image/jpeg jpg;}}}'.split('\n'))
|
||||
|
||||
def test_parse_from_file(self):
|
||||
with open(util.get_data_filename('foo.conf')) as handle:
|
||||
|
|
@ -116,24 +115,29 @@ class TestRawNginxParser(unittest.TestCase):
|
|||
|
||||
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']]]]])
|
||||
parsed = load(handle)
|
||||
parsed[-1][-1].append(UnspacedList([['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)
|
||||
parsed_new = load(handle)
|
||||
try:
|
||||
self.maxDiff = None
|
||||
self.assertEquals(parsed[0], parsed_new[0])
|
||||
self.assertEquals(parsed[1:], parsed_new[1:])
|
||||
finally:
|
||||
os.unlink(util.get_data_filename('nginx.new.conf'))
|
||||
|
||||
def test_comments(self):
|
||||
with open(util.get_data_filename('minimalistic_comments.conf')) as handle:
|
||||
|
|
@ -145,20 +149,23 @@ class TestRawNginxParser(unittest.TestCase):
|
|||
with open(util.get_data_filename('minimalistic_comments.new.conf')) as handle:
|
||||
parsed_new = load(handle)
|
||||
|
||||
self.assertEquals(parsed, parsed_new)
|
||||
try:
|
||||
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;']]],
|
||||
])
|
||||
self.assertEqual(parsed_new, [
|
||||
['#', " 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;']]],
|
||||
])
|
||||
finally:
|
||||
os.unlink(util.get_data_filename('minimalistic_comments.new.conf'))
|
||||
|
||||
def test_issue_518(self):
|
||||
parsed = loads('if ($http_accept ~* "webp") { set $webp "true"; }')
|
||||
|
|
@ -168,5 +175,73 @@ class TestRawNginxParser(unittest.TestCase):
|
|||
[['set', '$webp "true"']]]
|
||||
])
|
||||
|
||||
class TestUnspacedList(unittest.TestCase):
|
||||
"""Test the UnspacedList data structure"""
|
||||
def setUp(self):
|
||||
self.a = ["\n ", "things", " ", "quirk"]
|
||||
self.b = ["y", " "]
|
||||
self.l = self.a[:]
|
||||
self.l2 = self.b[:]
|
||||
self.ul = UnspacedList(self.l)
|
||||
self.ul2 = UnspacedList(self.l2)
|
||||
|
||||
def test_construction(self):
|
||||
self.assertEqual(self.ul, ["things", "quirk"])
|
||||
self.assertEqual(self.ul2, ["y"])
|
||||
|
||||
def test_append(self):
|
||||
ul3 = copy.deepcopy(self.ul)
|
||||
ul3.append("wise")
|
||||
self.assertEqual(ul3, ["things", "quirk", "wise"])
|
||||
self.assertEqual(ul3.spaced, self.a + ["wise"])
|
||||
|
||||
def test_add(self):
|
||||
ul3 = self.ul + self.ul2
|
||||
self.assertEqual(ul3, ["things", "quirk", "y"])
|
||||
self.assertEqual(ul3.spaced, self.a + self.b)
|
||||
self.assertEqual(self.ul.spaced, self.a)
|
||||
ul3 = self.ul + self.l2
|
||||
self.assertEqual(ul3, ["things", "quirk", "y"])
|
||||
self.assertEqual(ul3.spaced, self.a + self.b)
|
||||
|
||||
def test_extend(self):
|
||||
ul3 = copy.deepcopy(self.ul)
|
||||
ul3.extend(self.ul2)
|
||||
self.assertEqual(ul3, ["things", "quirk", "y"])
|
||||
self.assertEqual(ul3.spaced, self.a + self.b)
|
||||
self.assertEqual(self.ul.spaced, self.a)
|
||||
|
||||
def test_set(self):
|
||||
ul3 = copy.deepcopy(self.ul)
|
||||
ul3[0] = "zither"
|
||||
l = ["\n ", "zather", "zest"]
|
||||
ul3[1] = UnspacedList(l)
|
||||
self.assertEqual(ul3, ["zither", ["zather", "zest"]])
|
||||
self.assertEqual(ul3.spaced, [self.a[0], "zither", " ", l])
|
||||
|
||||
def test_get(self):
|
||||
self.assertRaises(IndexError, self.ul2.__getitem__, 2)
|
||||
self.assertRaises(IndexError, self.ul2.__getitem__, -3)
|
||||
|
||||
def test_rawlists(self):
|
||||
ul3 = copy.deepcopy(self.ul)
|
||||
ul3.insert(0, "some")
|
||||
ul3.append("why")
|
||||
ul3.extend(["did", "whether"])
|
||||
del ul3[2]
|
||||
self.assertEqual(ul3, ["some", "things", "why", "did", "whether"])
|
||||
|
||||
def test_is_dirty(self):
|
||||
self.assertEqual(False, self.ul2.is_dirty())
|
||||
ul3 = UnspacedList([])
|
||||
ul3.append(self.ul)
|
||||
self.assertEqual(False, self.ul.is_dirty())
|
||||
self.assertEqual(True, ul3.is_dirty())
|
||||
ul4 = UnspacedList([[1], [2, 3, 4]])
|
||||
self.assertEqual(False, ul4.is_dirty())
|
||||
ul4[1][2] = 5
|
||||
self.assertEqual(True, ul4.is_dirty())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main() # pragma: no cover
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ class NginxParserTest(util.NginxTest):
|
|||
|
||||
def test_filedump(self):
|
||||
nparser = parser.NginxParser(self.config_path, self.ssl_options)
|
||||
nparser.filedump('test')
|
||||
nparser.filedump('test', lazy=False)
|
||||
# pylint: disable=protected-access
|
||||
parsed = nparser._parse_files(nparser.abs_path(
|
||||
'sites-enabled/example.com.test'))
|
||||
|
|
@ -126,7 +126,7 @@ class NginxParserTest(util.NginxTest):
|
|||
nparser.add_server_directives(nparser.abs_path('nginx.conf'),
|
||||
set(['localhost',
|
||||
r'~^(www\.)?(example|bar)\.']),
|
||||
[['foo', 'bar'], ['ssl_certificate',
|
||||
[['foo', 'bar'], ['\n ', 'ssl_certificate', ' ',
|
||||
'/etc/ssl/cert.pem']],
|
||||
replace=False)
|
||||
ssl_re = re.compile(r'\n\s+ssl_certificate /etc/ssl/cert.pem')
|
||||
|
|
|
|||
|
|
@ -1,11 +0,0 @@
|
|||
# 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;
|
||||
}
|
||||
|
|
@ -1,83 +0,0 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -80,7 +80,7 @@ class TlsSniPerformTest(util.NginxTest):
|
|||
|
||||
mock_setup_cert.assert_called_once_with(self.achalls[0])
|
||||
self.assertEqual([response], responses)
|
||||
self.assertEqual(mock_save.call_count, 2)
|
||||
self.assertEqual(mock_save.call_count, 1)
|
||||
|
||||
# Make sure challenge config is included in main config
|
||||
http = self.sni.configurator.parser.parsed[
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
"""Common utilities for certbot_nginx."""
|
||||
import copy
|
||||
import os
|
||||
import pkg_resources
|
||||
import unittest
|
||||
|
|
@ -82,12 +83,15 @@ def filter_comments(tree):
|
|||
|
||||
def traverse(tree):
|
||||
"""Generator dropping comment nodes"""
|
||||
for key, values in tree:
|
||||
for entry in tree:
|
||||
key, values = entry
|
||||
if isinstance(key, list):
|
||||
yield [key, filter_comments(values)]
|
||||
new = copy.deepcopy(entry)
|
||||
new[1] = filter_comments(values)
|
||||
yield new
|
||||
else:
|
||||
if key != '#':
|
||||
yield [key, values]
|
||||
yield entry
|
||||
|
||||
return list(traverse(tree))
|
||||
|
||||
|
|
|
|||
|
|
@ -46,8 +46,6 @@ class NginxTlsSni01(common.TLSSNI01):
|
|||
if not self.achalls:
|
||||
return []
|
||||
|
||||
self.configurator.save()
|
||||
|
||||
addresses = []
|
||||
default_addr = "{0} default_server ssl".format(
|
||||
self.configurator.config.tls_sni_01_port)
|
||||
|
|
@ -93,10 +91,10 @@ class NginxTlsSni01(common.TLSSNI01):
|
|||
# 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]
|
||||
include_directive = ['include', ' ', self.challenge_conf]
|
||||
root = self.configurator.parser.loc["root"]
|
||||
|
||||
bucket_directive = ['server_names_hash_bucket_size', '128']
|
||||
bucket_directive = ['server_names_hash_bucket_size', ' ', '128']
|
||||
|
||||
main = self.configurator.parser.parsed[root]
|
||||
for key, body in main:
|
||||
|
|
@ -118,6 +116,7 @@ class NginxTlsSni01(common.TLSSNI01):
|
|||
|
||||
config = [self._make_server_block(pair[0], pair[1])
|
||||
for pair in itertools.izip(self.achalls, ll_addrs)]
|
||||
config = nginxparser.UnspacedList(config)
|
||||
|
||||
self.configurator.reverter.register_file_creation(
|
||||
True, self.challenge_conf)
|
||||
|
|
@ -142,19 +141,19 @@ class NginxTlsSni01(common.TLSSNI01):
|
|||
document_root = os.path.join(
|
||||
self.configurator.config.work_dir, "tls_sni_01_page")
|
||||
|
||||
block = [['listen', str(addr)] for addr in addrs]
|
||||
block = [['listen', ' ', str(addr)] for addr in addrs]
|
||||
|
||||
block.extend([['server_name',
|
||||
block.extend([['server_name', ' ',
|
||||
achall.response(achall.account_key).z_domain],
|
||||
['include', self.configurator.parser.loc["ssl_options"]],
|
||||
['include', ' ', self.configurator.parser.loc["ssl_options"]],
|
||||
# access and error logs necessary for
|
||||
# integration testing (non-root)
|
||||
['access_log', os.path.join(
|
||||
['access_log', ' ', os.path.join(
|
||||
self.configurator.config.work_dir, 'access.log')],
|
||||
['error_log', os.path.join(
|
||||
['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]]]])
|
||||
['ssl_certificate', ' ', self.get_cert_path(achall)],
|
||||
['ssl_certificate_key', ' ', self.get_key_path(achall)],
|
||||
[['location', ' ', '/'], [['root', ' ', document_root]]]])
|
||||
|
||||
return [['server'], block]
|
||||
|
|
|
|||
|
|
@ -773,7 +773,7 @@ def prepare_and_parse_args(plugins, args, detect_defaults=False): # pylint: dis
|
|||
helpful.add(
|
||||
"security", "--hsts", action="store_true",
|
||||
help="Add the Strict-Transport-Security header to every HTTP response."
|
||||
" Forcing browser to use always use SSL for the domain."
|
||||
" Forcing browser to always use SSL for the domain."
|
||||
" Defends against SSL Stripping.", dest="hsts", default=False)
|
||||
helpful.add(
|
||||
"security", "--no-hsts", action="store_false",
|
||||
|
|
|
|||
|
|
@ -282,7 +282,7 @@ class Client(object):
|
|||
"by your operating system package manager")
|
||||
|
||||
if self.config.dry_run:
|
||||
logger.info("Dry run: Skipping creating new lineage for %s",
|
||||
logger.debug("Dry run: Skipping creating new lineage for %s",
|
||||
domains[0])
|
||||
return None
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ CLI_DEFAULTS = dict(
|
|||
os.path.join(os.environ.get("XDG_CONFIG_HOME", "~/.config"),
|
||||
"letsencrypt", "cli.ini"),
|
||||
],
|
||||
verbose_count=-(logging.WARNING / 10),
|
||||
verbose_count=-(logging.INFO / 10),
|
||||
server="https://acme-v01.api.letsencrypt.org/directory",
|
||||
rsa_key_size=2048,
|
||||
rollback_checkpoints=1,
|
||||
|
|
|
|||
|
|
@ -180,6 +180,9 @@ class IAuthenticator(IPlugin):
|
|||
def cleanup(achalls):
|
||||
"""Revert changes and shutdown after challenges complete.
|
||||
|
||||
This method should be able to revert all changes made by
|
||||
perform, even if perform exited abnormally.
|
||||
|
||||
:param list achalls: Non-empty (guaranteed) list of
|
||||
:class:`~certbot.achallenges.AnnotatedChallenge`
|
||||
instances, a subset of those previously passed to :func:`perform`.
|
||||
|
|
@ -238,6 +241,14 @@ class IInstaller(IPlugin):
|
|||
|
||||
Represents any server that an X509 certificate can be placed.
|
||||
|
||||
It is assumed that :func:`save` is the only method that finalizes a
|
||||
checkpoint. This is important to ensure that checkpoints are
|
||||
restored in a consistent manner if requested by the user or in case
|
||||
of an error.
|
||||
|
||||
Using :class:`certbot.reverter.Reverter` to implement checkpoints,
|
||||
rollback, and recovery can dramatically simplify plugin development.
|
||||
|
||||
"""
|
||||
|
||||
def get_all_names():
|
||||
|
|
@ -304,8 +315,11 @@ class IInstaller(IPlugin):
|
|||
|
||||
Both title and temporary are needed because a save may be
|
||||
intended to be permanent, but the save is not ready to be a full
|
||||
checkpoint. If an exception is raised, it is assumed a new
|
||||
checkpoint was not created.
|
||||
checkpoint.
|
||||
|
||||
It is assumed that at most one checkpoint is finalized by this
|
||||
method. Additionally, if an exception is raised, it is assumed a
|
||||
new checkpoint was not finalized.
|
||||
|
||||
: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
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
"""Certbot main entry point."""
|
||||
from __future__ import print_function
|
||||
import atexit
|
||||
import dialog
|
||||
import errno
|
||||
import functools
|
||||
import logging.handlers
|
||||
|
|
@ -83,14 +84,17 @@ def _auth_from_domains(le_client, config, domains, lineage=None):
|
|||
if action == "reinstall":
|
||||
# The lineage already exists; allow the caller to try installing
|
||||
# it without getting a new certificate at all.
|
||||
logger.info("Keeping the existing certificate")
|
||||
return lineage, "reinstall"
|
||||
|
||||
hooks.pre_hook(config)
|
||||
try:
|
||||
if action == "renew":
|
||||
logger.info("Renewing an existing certificate")
|
||||
renewal.renew_cert(config, domains, le_client, lineage)
|
||||
elif action == "newcert":
|
||||
# TREAT AS NEW REQUEST
|
||||
logger.info("Obtaining a new certificate")
|
||||
lineage = le_client.obtain_and_enroll_certificate(domains)
|
||||
if lineage is False:
|
||||
raise errors.Error("Certificate could not be obtained")
|
||||
|
|
@ -526,7 +530,7 @@ def _csr_obtain_cert(config, le_client):
|
|||
csr, typ = config.actual_csr
|
||||
certr, chain = le_client.obtain_certificate_from_csr(config.domains, csr, typ)
|
||||
if config.dry_run:
|
||||
logger.info(
|
||||
logger.debug(
|
||||
"Dry run: skipping saving certificate to %s", config.cert_path)
|
||||
else:
|
||||
cert_path, _, cert_fullchain = le_client.save_certificate(
|
||||
|
|
@ -624,11 +628,12 @@ def _cli_log_handler(config, level, fmt):
|
|||
|
||||
def setup_logging(config, cli_handler_factory, logfile):
|
||||
"""Setup logging."""
|
||||
fmt = "%(asctime)s:%(levelname)s:%(name)s:%(message)s"
|
||||
file_fmt = "%(asctime)s:%(levelname)s:%(name)s:%(message)s"
|
||||
cli_fmt = "%(message)s"
|
||||
level = -config.verbose_count * 10
|
||||
file_handler, log_file_path = setup_log_file_handler(
|
||||
config, logfile=logfile, fmt=fmt)
|
||||
cli_handler = cli_handler_factory(config, level, fmt)
|
||||
config, logfile=logfile, fmt=file_fmt)
|
||||
cli_handler = cli_handler_factory(config, level, cli_fmt)
|
||||
|
||||
# TODO: use fileConfig?
|
||||
|
||||
|
|
@ -674,7 +679,10 @@ def _handle_exception(exc_type, exc_value, trace, config):
|
|||
# Here we're passing a client or ACME error out to the client at the shell
|
||||
# Tell the user a bit about what happened, without overwhelming
|
||||
# them with a full traceback
|
||||
err = traceback.format_exception_only(exc_type, exc_value)[0]
|
||||
if issubclass(exc_type, dialog.error):
|
||||
err = exc_value.complete_message()
|
||||
else:
|
||||
err = traceback.format_exception_only(exc_type, exc_value)[0]
|
||||
# Typical error from the ACME module:
|
||||
# acme.messages.Error: urn:acme:error:malformed :: The request message was
|
||||
# malformed :: Error creating new registration :: Validation of contact
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ def _restore_webroot_config(config, renewalparams):
|
|||
if not cli.set_by_cli("webroot_map"):
|
||||
config.namespace.webroot_map = renewalparams["webroot_map"]
|
||||
elif "webroot_path" in renewalparams:
|
||||
logger.info("Ancient renewal conf file without webroot-map, restoring webroot-path")
|
||||
logger.debug("Ancient renewal conf file without webroot-map, restoring webroot-path")
|
||||
wp = renewalparams["webroot_path"]
|
||||
if isinstance(wp, str): # prior to 0.1.0, webroot_path was a string
|
||||
wp = [wp]
|
||||
|
|
@ -194,7 +194,7 @@ def _restore_required_config_elements(config, renewalparams):
|
|||
def should_renew(config, lineage):
|
||||
"Return true if any of the circumstances for automatic renewal apply."
|
||||
if config.renew_by_default:
|
||||
logger.info("Auto-renewal forced with --force-renewal...")
|
||||
logger.debug("Auto-renewal forced with --force-renewal...")
|
||||
return True
|
||||
if lineage.should_autorenew(interactive=True):
|
||||
logger.info("Cert is due for renewal, auto-renewing...")
|
||||
|
|
@ -236,7 +236,7 @@ def renew_cert(config, domains, le_client, lineage):
|
|||
_avoid_invalidating_lineage(config, lineage, original_server)
|
||||
new_certr, new_chain, new_key, _ = le_client.obtain_certificate(domains)
|
||||
if config.dry_run:
|
||||
logger.info("Dry run: skipping updating lineage at %s",
|
||||
logger.debug("Dry run: skipping updating lineage at %s",
|
||||
os.path.dirname(lineage.cert))
|
||||
else:
|
||||
prior_version = lineage.latest_common_version()
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ class Reporter(object):
|
|||
"""
|
||||
assert self.HIGH_PRIORITY <= priority <= self.LOW_PRIORITY
|
||||
self.messages.put(self._msg_type(priority, msg, on_crash))
|
||||
logger.info("Reporting to user: %s", msg)
|
||||
logger.debug("Reporting to user: %s", msg)
|
||||
|
||||
def atexit_print_messages(self, pid=None):
|
||||
"""Function to be registered with atexit to print messages.
|
||||
|
|
|
|||
|
|
@ -24,6 +24,39 @@ logger = logging.getLogger(__name__)
|
|||
class Reverter(object):
|
||||
"""Reverter Class - save and revert configuration checkpoints.
|
||||
|
||||
This class can be used by the plugins, especially Installers, to
|
||||
undo changes made to the user's system. Modifications to files and
|
||||
commands to do undo actions taken by the plugin should be registered
|
||||
with this class before the action is taken.
|
||||
|
||||
Once a change has been registered with this class, there are three
|
||||
states the change can be in. First, the change can be a temporary
|
||||
change. This should be used for changes that will soon be reverted,
|
||||
such as config changes for the purpose of solving a challenge.
|
||||
Changes are added to this state through calls to
|
||||
:func:`~add_to_temp_checkpoint` and reverted when
|
||||
:func:`~revert_temporary_config` or :func:`~recovery_routine` is
|
||||
called.
|
||||
|
||||
The second state a change can be in is in progress. These changes
|
||||
are not temporary, however, they also have not been finalized in a
|
||||
checkpoint. A change must become in progress before it can be
|
||||
finalized. Changes are added to this state through calls to
|
||||
:func:`~add_to_checkpoint` and reverted when
|
||||
:func:`~recovery_routine` is called.
|
||||
|
||||
The last state a change can be in is finalized in a checkpoint. A
|
||||
change is put into this state by first becoming an in progress
|
||||
change and then calling :func:`~finalize_checkpoint`. Changes
|
||||
in this state can be reverted through calls to
|
||||
:func:`~rollback_checkpoints`.
|
||||
|
||||
As a final note, creating new files and registering undo commands
|
||||
are handled specially and use the methods
|
||||
:func:`~register_file_creation` and :func:`~register_undo_command`
|
||||
respectively. Both of these methods can be used to create either
|
||||
temporary or in progress changes.
|
||||
|
||||
.. note:: Consider moving everything over to CSV format.
|
||||
|
||||
:param config: Configuration.
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
import dialog
|
||||
import functools
|
||||
import itertools
|
||||
import os
|
||||
|
|
@ -341,11 +342,11 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
|
|||
# FQDN
|
||||
self.assertRaises(errors.ConfigurationError,
|
||||
self._call,
|
||||
['-d', 'comma,gotwrong.tld'])
|
||||
['-d', 'a' * 64])
|
||||
# FQDN 2
|
||||
self.assertRaises(errors.ConfigurationError,
|
||||
self._call,
|
||||
['-d', 'illegal.character=.tld'])
|
||||
['-d', (('a' * 50) + '.') * 10])
|
||||
# Wildcard
|
||||
self.assertRaises(errors.ConfigurationError,
|
||||
self._call,
|
||||
|
|
@ -922,6 +923,13 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
|
|||
mock_sys.exit.assert_called_with(''.join(
|
||||
traceback.format_exception_only(KeyboardInterrupt, interrupt)))
|
||||
|
||||
# Test dialog errors
|
||||
exception = dialog.error(message="test message")
|
||||
main._handle_exception(
|
||||
dialog.DialogError, exc_value=exception, trace=None, config=None)
|
||||
error_msg = mock_sys.exit.call_args_list[-1][0][0]
|
||||
self.assertTrue("test message" in error_msg)
|
||||
|
||||
def test_read_file(self):
|
||||
rel_test_path = os.path.relpath(os.path.join(self.tmp_dir, 'foo'))
|
||||
self.assertRaises(
|
||||
|
|
|
|||
|
|
@ -248,9 +248,9 @@ class ChooseNamesTest(unittest.TestCase):
|
|||
def test_get_valid_domains(self):
|
||||
from certbot.display.ops import get_valid_domains
|
||||
all_valid = ["example.com", "second.example.com",
|
||||
"also.example.com"]
|
||||
all_invalid = ["xn--ls8h.tld", "*.wildcard.com", "notFQDN",
|
||||
"uniçodé.com"]
|
||||
"also.example.com", "under_score.example.com",
|
||||
"justtld"]
|
||||
all_invalid = ["xn--ls8h.tld", "*.wildcard.com", "uniçodé.com"]
|
||||
two_valid = ["example.com", "xn--ls8h.tld", "also.example.com"]
|
||||
self.assertEqual(get_valid_domains(all_valid), all_valid)
|
||||
self.assertEqual(get_valid_domains(all_invalid), [])
|
||||
|
|
@ -276,19 +276,18 @@ class ChooseNamesTest(unittest.TestCase):
|
|||
mock_util().input.return_value = (display_util.OK,
|
||||
"xn--ls8h.tld")
|
||||
self.assertEqual(_choose_names_manually(), [])
|
||||
# non-FQDN and no retry
|
||||
mock_util().input.return_value = (display_util.OK,
|
||||
"notFQDN")
|
||||
self.assertEqual(_choose_names_manually(), [])
|
||||
# Two valid domains
|
||||
# Valid domains
|
||||
mock_util().input.return_value = (display_util.OK,
|
||||
("example.com,"
|
||||
"under_score.example.com,"
|
||||
"justtld,"
|
||||
"valid.example.com"))
|
||||
self.assertEqual(_choose_names_manually(),
|
||||
["example.com", "valid.example.com"])
|
||||
["example.com", "under_score.example.com",
|
||||
"justtld", "valid.example.com"])
|
||||
# Three iterations
|
||||
mock_util().input.return_value = (display_util.OK,
|
||||
"notFQDN")
|
||||
"uniçodé.com")
|
||||
yn = mock.MagicMock()
|
||||
yn.side_effect = [True, True, False]
|
||||
mock_util().yesno = yn
|
||||
|
|
|
|||
|
|
@ -423,14 +423,17 @@ def enforce_domain_sanity(domain):
|
|||
# It wasn't an IP address, so that's good
|
||||
pass
|
||||
|
||||
# FQDN checks from
|
||||
# http://www.mkyong.com/regular-expressions/domain-name-regular-expression-example/
|
||||
# Characters used, domain parts < 63 chars, tld > 1 < 64 chars
|
||||
# first and last char is not "-"
|
||||
fqdn = re.compile("^((?!-)[A-Za-z0-9-]{1,63}(?<!-)\\.)+[A-Za-z]{2,63}$")
|
||||
if not fqdn.match(domain):
|
||||
raise errors.ConfigurationError("Requested domain {0} is not a FQDN"
|
||||
.format(domain))
|
||||
# FQDN checks according to RFC 2181: domain name should be less than 255
|
||||
# octets (inclusive). And each label is 1 - 63 octets (inclusive).
|
||||
# https://tools.ietf.org/html/rfc2181#section-11
|
||||
msg = "Requested domain {0} is not a FQDN because ".format(domain)
|
||||
labels = domain.split('.')
|
||||
for l in labels:
|
||||
if not 0 < len(l) < 64:
|
||||
raise errors.ConfigurationError(msg + "label {0} is too long.".format(l))
|
||||
if len(domain) > 255:
|
||||
raise errors.ConfigurationError(msg + "it is too long.")
|
||||
|
||||
return domain
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ security:
|
|||
HTTPS for the newly authenticated vhost. (default:
|
||||
None)
|
||||
--hsts Add the Strict-Transport-Security header to every HTTP
|
||||
response. Forcing browser to use always use SSL for
|
||||
response. Forcing browser to always use SSL for
|
||||
the domain. Defends against SSL Stripping. (default:
|
||||
False)
|
||||
--no-hsts Do not automatically add the Strict-Transport-Security
|
||||
|
|
@ -200,6 +200,15 @@ renew:
|
|||
and keys; the shell variable $RENEWED_DOMAINS will
|
||||
contain a space-delimited list of renewed cert domains
|
||||
(default: None)
|
||||
--disable-hook-validation
|
||||
Ordinarily the commands specified for --pre-hook
|
||||
/--post-hook/--renew-hook will be checked for
|
||||
validity, to see if the programs being run are in the
|
||||
$PATH, so that mistakes can be caught early, even when
|
||||
the hooks aren't being run just yet. The validation is
|
||||
rather simplistic and fails if you use more advanced
|
||||
shell constructs, so you can use this switch to
|
||||
disable it. (default: True)
|
||||
|
||||
certonly:
|
||||
Options for modifying how a cert is obtained
|
||||
|
|
|
|||
|
|
@ -71,6 +71,9 @@ The following tools are there to help you:
|
|||
experimental, non-production Apache2 install on them. ``tox -e
|
||||
apacheconftest`` can be used to run those specific Apache conf tests.
|
||||
|
||||
- ``tox --skip-missing-interpreters`` runs tox while ignoring missing versions
|
||||
of Python needed for running the tests.
|
||||
|
||||
- ``tox -e py27``, ``tox -e py26`` etc, run unit tests for specific Python
|
||||
versions.
|
||||
|
||||
|
|
@ -313,7 +316,9 @@ Steps:
|
|||
3. Run ``./pep8.travis.sh`` to do a cursory check of your code style.
|
||||
Fix any errors.
|
||||
4. Run ``tox -e lint`` to check for pylint errors. Fix any errors.
|
||||
5. Run ``tox`` to run the entire test suite including coverage. Fix any errors.
|
||||
5. Run ``tox --skip-missing-interpreters`` to run the entire test suite
|
||||
including coverage. The ``--skip-missing-interpreters`` argument ignores
|
||||
missing versions of Python needed for running the tests. Fix any errors.
|
||||
6. If your code touches communication with an ACME server/Boulder, you
|
||||
should run the integration tests, see `integration`_. See `Known Issues`_
|
||||
for some common failures that have nothing to do with your code.
|
||||
|
|
|
|||
|
|
@ -552,10 +552,3 @@ Beyond the methods discussed here, other methods may be possible, such as
|
|||
installing Certbot directly with pip from PyPI or downloading a ZIP
|
||||
archive from GitHub may be technically possible but are not presently
|
||||
recommended or supported.
|
||||
|
||||
|
||||
.. rubric:: Footnotes
|
||||
|
||||
.. [#venv] By using this virtualized Python environment (`virtualenv
|
||||
<https://virtualenv.pypa.io>`_) we don't pollute the main
|
||||
OS space with packages from PyPI!
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ XDG_DATA_HOME=${XDG_DATA_HOME:-~/.local/share}
|
|||
VENV_NAME="letsencrypt"
|
||||
VENV_PATH=${VENV_PATH:-"$XDG_DATA_HOME/$VENV_NAME"}
|
||||
VENV_BIN="$VENV_PATH/bin"
|
||||
LE_AUTO_VERSION="0.8.0"
|
||||
LE_AUTO_VERSION="0.8.1"
|
||||
BASENAME=$(basename $0)
|
||||
USAGE="Usage: $BASENAME [OPTIONS]
|
||||
A self-updating wrapper script for the Certbot ACME client. When run, updates
|
||||
|
|
@ -172,7 +172,7 @@ BootstrapDebCommon() {
|
|||
# distro version (#346)
|
||||
|
||||
virtualenv=
|
||||
if apt-cache show virtualenv > /dev/null 2>&1; then
|
||||
if apt-cache show virtualenv > /dev/null 2>&1 && ! apt-cache --quiet=0 show virtualenv 2>&1 | grep -q 'No packages found'; then
|
||||
virtualenv="virtualenv"
|
||||
fi
|
||||
|
||||
|
|
@ -458,12 +458,39 @@ BootstrapSmartOS() {
|
|||
pkgin -y install 'gcc49' 'py27-augeas' 'py27-virtualenv'
|
||||
}
|
||||
|
||||
BootstrapMageiaCommon() {
|
||||
if ! $SUDO urpmi --force \
|
||||
python \
|
||||
libpython-devel \
|
||||
python-virtualenv
|
||||
then
|
||||
echo "Could not install Python dependencies. Aborting bootstrap!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! $SUDO urpmi --force \
|
||||
git \
|
||||
gcc \
|
||||
cdialog \
|
||||
python-augeas \
|
||||
libopenssl-devel \
|
||||
libffi-devel \
|
||||
rootcerts
|
||||
then
|
||||
echo "Could not install additional dependencies. Aborting bootstrap!"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
# Install required OS packages:
|
||||
Bootstrap() {
|
||||
if [ -f /etc/debian_version ]; then
|
||||
echo "Bootstrapping dependencies for Debian-based OSes..."
|
||||
BootstrapDebCommon
|
||||
elif [ -f /etc/mageia-release ] ; then
|
||||
# Mageia has both /etc/mageia-release and /etc/redhat-release
|
||||
ExperimentalBootstrap "Mageia" BootstrapMageiaCommon
|
||||
elif [ -f /etc/redhat-release ]; then
|
||||
echo "Bootstrapping dependencies for RedHat-based OSes..."
|
||||
BootstrapRpmCommon
|
||||
|
|
@ -476,7 +503,7 @@ Bootstrap() {
|
|||
BootstrapArchCommon
|
||||
else
|
||||
echo "Please use pacman to install letsencrypt packages:"
|
||||
echo "# pacman -S letsencrypt letsencrypt-apache"
|
||||
echo "# pacman -S certbot certbot-apache"
|
||||
echo
|
||||
echo "If you would like to use the virtualenv way, please run the script again with the"
|
||||
echo "--debug flag."
|
||||
|
|
@ -500,6 +527,7 @@ Bootstrap() {
|
|||
echo "You will need to bootstrap, configure virtualenv, and run pip install manually."
|
||||
echo "Please see https://letsencrypt.readthedocs.org/en/latest/contributing.html#prerequisites"
|
||||
echo "for more info."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
|
|
@ -719,15 +747,15 @@ letsencrypt==0.7.0 \
|
|||
|
||||
# THE LINES BELOW ARE EDITED BY THE RELEASE SCRIPT; ADD ALL DEPENDENCIES ABOVE.
|
||||
|
||||
acme==0.8.0 \
|
||||
--hash=sha256:8561d590e496afb41a8ff2dac389199661d9cd785b1636ae08325771511189af \
|
||||
--hash=sha256:dfa86b547628b231f275c7e0efc7a09bec5dfaec866f89f5c5b59b78c14564da
|
||||
certbot==0.8.0 \
|
||||
--hash=sha256:395c5840ff6b75aa51ee6449c86d016c14c5f65a71281e7bcef5feecac6a3293 \
|
||||
--hash=sha256:3c3c70b484fb3243a166515adc81ae0401c5d687a2763c75b40df9d8241a4314
|
||||
certbot-apache==0.8.0 \
|
||||
--hash=sha256:f4d4fc962ecc19646f6745d49c62a265d26e5b2df3acf34ef4865351594156e3 \
|
||||
--hash=sha256:cfb211debbcb0d0645c88d7e8bb38c591fca263bfdb5337242c023956055e268
|
||||
acme==0.8.1 \
|
||||
--hash=sha256:ccd7883772efbf933f91713b8241455993834f3620c8fbd459d9ed5e50bbaaca \
|
||||
--hash=sha256:d3ea4acf280bf6253ad7d641cb0970f230a19805acfed809e7a8ddcf62157d9f
|
||||
certbot==0.8.1 \
|
||||
--hash=sha256:89805d9f70249ae859ec4d7a99c00b4bb7083ca90cd12d4d202b76dfc284f7c5 \
|
||||
--hash=sha256:6ca8df3d310ced6687d38aac17c0fb8c1b2ec7a3bea156a254e4cc2a1c132771
|
||||
certbot-apache==0.8.1 \
|
||||
--hash=sha256:c9e3fdc15e65589c2e39eb0e6b1f61f0c0a1db3c17b00bb337f0ff636cc61cb3 \
|
||||
--hash=sha256:0faf2879884d3b7a58b071902fba37d4b8b58a50e2c3b8ac262c0a74134045ed
|
||||
|
||||
UNLIKELY_EOF
|
||||
# -------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
-----BEGIN PGP SIGNATURE-----
|
||||
Version: GnuPG v1
|
||||
|
||||
iQEcBAABAgAGBQJXUJvwAAoJEE0XyZXNl3XyvKsH/3qn7Xa/GQx3HvB6Io/Csn/E
|
||||
v1nbUg5RPwvrTyyol8BJ6UrHiJw+gTbUgCAnBkZ7DYKaC8AQmQXVRcWXNALMMTzB
|
||||
6LpBXjQQ2xrBYamGj70N7KnTM1QmxI96GUQouiHMJVugV4uihKJDjtR8/f2JWKok
|
||||
ZSox6E4LqC45HzqLWiOqc13TrHbti32Mo8DyC63PBnSwMnypGLK6XcqM0L9Re62W
|
||||
smoKu1VWKwWZYRYXIQr0dvK4JmVTrIsdASdZkhTC/vc8y4tGkdN0DcF2EHzci6OA
|
||||
Tx0W+Ao+HM1ZcaaH3BJ1y3kYfT+mlt6o4OaK3UB/wtUzMmVih7l1UeiNkVL0oYk=
|
||||
=t3L6
|
||||
iQEcBAABAgAGBQJXYJmBAAoJEE0XyZXNl3XyyIMH/jtYFb7rl5XXN8hjlKuK5frq
|
||||
z7/jdK7fvI+mtYJ4i2Cy3yMz8T4wscXGkhxNtipbATWlpevPfjYzm4ZGC25coFZx
|
||||
fDX44w0hBBgel7EISXGR1ABXb2rj24TZxIYXwaeClylsK9n5CxcWBocn8tDlfr8t
|
||||
7VQUJEL3l1IlrnKnvpoL4Eq11sxlIPtitDPJ5c98ZM1293ZbWzIqyZKoXLIUkKHg
|
||||
pkaa80j/QMmFumxzXFenU91JusLdeoblvjjg+kzjGonjslAYIuH4wEEjz2VJuUYe
|
||||
P2+2ZyW4eLA6rRZhZ3CMtV79HzTPTWiELCYbXezb+yXJJEqzCYtIXkmbNQ3jUEY=
|
||||
=86lB
|
||||
-----END PGP SIGNATURE-----
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ Help for certbot itself cannot be provided until it is installed.
|
|||
-n, --non-interactive, --noninteractive run without asking for user input
|
||||
--no-self-upgrade do not download updates
|
||||
--os-packages-only install OS dependencies and exit
|
||||
-q, --quiet provide only update/error output
|
||||
-v, --verbose provide more output
|
||||
|
||||
All arguments are accepted and forwarded to the Certbot client when run."
|
||||
|
|
@ -52,15 +53,19 @@ for arg in "$@" ; do
|
|||
HELP=1;;
|
||||
--noninteractive|--non-interactive)
|
||||
ASSUME_YES=1;;
|
||||
--quiet)
|
||||
QUIET=1;;
|
||||
--verbose)
|
||||
VERBOSE=1;;
|
||||
-[!-]*)
|
||||
while getopts ":hnv" short_arg $arg; do
|
||||
while getopts ":hnvq" short_arg $arg; do
|
||||
case "$short_arg" in
|
||||
h)
|
||||
HELP=1;;
|
||||
n)
|
||||
ASSUME_YES=1;;
|
||||
q)
|
||||
QUIET=1;;
|
||||
v)
|
||||
VERBOSE=1;;
|
||||
esac
|
||||
|
|
@ -598,28 +603,29 @@ ConfigArgParse==0.10.0 \
|
|||
--hash=sha256:3b50a83dd58149dfcee98cb6565265d10b53e9c0a2bca7eeef7fb5f5524890a7
|
||||
configobj==5.0.6 \
|
||||
--hash=sha256:a2f5650770e1c87fb335af19a9b7eb73fc05ccf22144eb68db7d00cd2bcb0902
|
||||
cryptography==1.2.3 \
|
||||
--hash=sha256:031938f73a5c5eb3e809e18ff7caeb6865351871417be6050cb8c86a9a202b9a \
|
||||
--hash=sha256:a179a38d50f8d68b491d7a313db78f8cabe290842cecddddc7b34d408e59db0a \
|
||||
--hash=sha256:906c88b2aadcf99cfabb24098263d1bf65ab0c8688acde10dae1f09d865920f1 \
|
||||
--hash=sha256:6e706c5c6088770b1d1b634e959e21963e315b0255f5f4777125ad3d54082977 \
|
||||
--hash=sha256:f5ebf8e31c48f8707921dca0e994de77813a9c9b9bf03c119c5ddf97bdcffe73 \
|
||||
--hash=sha256:c7b89e42288cc7fbee3812e99ef5c744f22452e11d6822f6807afc6d6b3be83e \
|
||||
--hash=sha256:8408d29865947109d8b68f1837a7cde1aa4dc86e0f79ca3ba58c0c44e443d6a5 \
|
||||
--hash=sha256:c7e76cf3c3d925dd31fa238cfb806cffba718c0f08707d77a538768477969956 \
|
||||
--hash=sha256:7d8de35380f31702758b7753bb5c40723832c73006dedb2f9099bf61a37f7287 \
|
||||
--hash=sha256:5edbee71fae5469ee83fe0a37866b9398c8ce3a46325c24fcedfbf097bb48a19 \
|
||||
--hash=sha256:594edafe4801c13bdc1cc305e7704a90c19617e95936f6ab457ee4ffe000ba50 \
|
||||
--hash=sha256:b7fdb16a0a7f481be42da744bfe1ea2163025de21f90f2c688a316f3c354da9c \
|
||||
--hash=sha256:207b8bf0fe0907336df38b733b487521cf9e138189aba9234ad54fe545dd0db8 \
|
||||
--hash=sha256:509a2f05386270cf783993c90d49ffefb3dd62aee45bf1ea8ce3d2cde7271c21 \
|
||||
--hash=sha256:ac69b65dd1af0179ede40c9f15788c88f73e628ea6c0519de3838e279bb388c6 \
|
||||
--hash=sha256:8df6fad6c6ae12fd7004ea29357f0a2b4d3774eaeca7656530d08d2d90cd41aa \
|
||||
--hash=sha256:0b8b96dd81cc1533a04f30382c0fe21c1972e189f794d0c4261a18cec08fd9b5 \
|
||||
--hash=sha256:cae8fca1883f23c50ea78d89de6fe4fefdb4cea83177760f47177559414ded93 \
|
||||
--hash=sha256:1a471ca576a9cdce1b1cd9f3a22b1d09ee44d46862037557de17919c0db44425 \
|
||||
--hash=sha256:8ec4e8e3d453b3a1b63b5f57737a434dcf1ee4a2f26f6ff7c5a37c3f679104d2 \
|
||||
--hash=sha256:8eb11c77dd8e73f48df6b2f7a7e16173fe0fe8fdfe266232832e88477e08454e
|
||||
cryptography==1.3.4 \
|
||||
--hash=sha256:bede00edd11a2a62c8c98c271cc103fa3a3d72acf64f6e5e4eaf251128897b17 \
|
||||
--hash=sha256:53b39e687b744bb548a98f40736cc529d9f60959b4e6cc551322cf9505d35eb3 \
|
||||
--hash=sha256:474b73ad1139b4e423e46bbd818efd0d5c0df1c65d9f7c957d64c9215d77afde \
|
||||
--hash=sha256:aaddf9592d5b99e32dd518bb4a25b147c124f9d6b4ad64b94f01b15d1666b8c8 \
|
||||
--hash=sha256:6dcad2f407db8c3cd6ecd78361439c449a4f94786b46c54507e7e68f51e1709d \
|
||||
--hash=sha256:475c153fc622e656f1f10a9c9941d0ac7ab18df7c38d35d563a437c1c0e34f24 \
|
||||
--hash=sha256:86dd61df581cba04e89e45081efbc531faff1c9d99c77b1ce97f87216c356353 \
|
||||
--hash=sha256:75cc697e4ef5fdd0102ca749114c6370dbd11db0c9132a18834858c2566247e3 \
|
||||
--hash=sha256:ea03ad5b9df6d79fc9fc1ab23729e01e1c920d2974c5e3c634ccf45a5c378452 \
|
||||
--hash=sha256:c8872b8fe4f3416d6338ab99612f49ab314f7856cb43bffab2a32d28a6267be8 \
|
||||
--hash=sha256:468fc6e16eaec6ceaa6bc341273e6e9912d01b42b740f8cf896ace7fcd6a321d \
|
||||
--hash=sha256:d6fea3c6502735011c5d61a62aef1c1d770fc6a2def45d9e6c0d94c9651e3317 \
|
||||
--hash=sha256:3cf95f179f4bead3d5649b91860ef4cf60ad4244209190fc405908272576d961 \
|
||||
--hash=sha256:141f77e60a5b9158309b2b60288c7f81d37faa15c22a69b94c190ceefaaa6236 \
|
||||
--hash=sha256:87b7a1fe703c6424451f3372d1879dae91c7fe5e13375441a72833db76fee30e \
|
||||
--hash=sha256:f5ee3cb0cf1a6550bf483ccffa6608db267a377b45f7e3a8201a86d1d8feb19f \
|
||||
--hash=sha256:4e097286651ea318300af3251375d48b71b8228481c56cd617ddd4459a1ff261 \
|
||||
--hash=sha256:1e3d3ae3f22f22d50d340f47f25227511326f3f1396c6d2446a5b45b516c4313 \
|
||||
--hash=sha256:6a057941cb64d79834ea3cf99093fcc4787c2a5d44f686c4f297361ddc419bcd \
|
||||
--hash=sha256:68b3d5390b92559ddd3353c73ab2dfcff758f9c4ec4f5d5226ccede0e5d779f4 \
|
||||
--hash=sha256:545dc003b4b6081f9c3e452da15d819b04b696f49484aff64c0a2aedf766bef8 \
|
||||
--hash=sha256:423ff890c01be7c70dbfeaa967eeef5146f1a43a5f810ffdc07b178e48a105a9
|
||||
enum34==1.1.2 \
|
||||
--hash=sha256:2475d7fcddf5951e92ff546972758802de5260bf409319a9f1934e6bbc8b1dc7 \
|
||||
--hash=sha256:35907defb0f992b75ab7788f65fedc1cf20ffa22688e0e6f6f12afc06b3ea501
|
||||
|
|
@ -679,9 +685,9 @@ pyasn1==0.1.9 \
|
|||
--hash=sha256:5191ff6b9126d2c039dd87f8ff025bed274baf07fa78afa46f556b1ad7265d6e \
|
||||
--hash=sha256:8323e03637b2d072cc7041300bac6ec448c3c28950ab40376036788e9a1af629 \
|
||||
--hash=sha256:853cacd96d1f701ddd67aa03ecc05f51890135b7262e922710112f12a2ed2a7f
|
||||
pyOpenSSL==0.15.1 \
|
||||
--hash=sha256:88e45e6bb25dfed272a1ef2e728461d44b634c2cd689e989b6e56a349c5a3ae5 \
|
||||
--hash=sha256:f0a26070d6db0881de8bcc7846934b7c3c930d8f9c79d45883ee48984bc0d672
|
||||
pyopenssl==16.0.0 \
|
||||
--hash=sha256:5add70cf00273bf957ca31fdb0df9b0ae4639e081897d5f86a0ae1f104901230 \
|
||||
--hash=sha256:363d10ee43d062285facf4e465f4f5163f9f702f9134f0a5896f134cbb92d17d
|
||||
pyRFC3339==1.0 \
|
||||
--hash=sha256:eea31835c56e2096af4363a5745a784878a61d043e247d3a6d6a0a32a9741f56 \
|
||||
--hash=sha256:8dfbc6c458b8daba1c0f3620a8c78008b323a268b27b7359e92a4ae41325f535
|
||||
|
|
@ -747,15 +753,15 @@ letsencrypt==0.7.0 \
|
|||
|
||||
# THE LINES BELOW ARE EDITED BY THE RELEASE SCRIPT; ADD ALL DEPENDENCIES ABOVE.
|
||||
|
||||
acme==0.8.0 \
|
||||
--hash=sha256:8561d590e496afb41a8ff2dac389199661d9cd785b1636ae08325771511189af \
|
||||
--hash=sha256:dfa86b547628b231f275c7e0efc7a09bec5dfaec866f89f5c5b59b78c14564da
|
||||
certbot==0.8.0 \
|
||||
--hash=sha256:395c5840ff6b75aa51ee6449c86d016c14c5f65a71281e7bcef5feecac6a3293 \
|
||||
--hash=sha256:3c3c70b484fb3243a166515adc81ae0401c5d687a2763c75b40df9d8241a4314
|
||||
certbot-apache==0.8.0 \
|
||||
--hash=sha256:f4d4fc962ecc19646f6745d49c62a265d26e5b2df3acf34ef4865351594156e3 \
|
||||
--hash=sha256:cfb211debbcb0d0645c88d7e8bb38c591fca263bfdb5337242c023956055e268
|
||||
acme==0.8.1 \
|
||||
--hash=sha256:ccd7883772efbf933f91713b8241455993834f3620c8fbd459d9ed5e50bbaaca \
|
||||
--hash=sha256:d3ea4acf280bf6253ad7d641cb0970f230a19805acfed809e7a8ddcf62157d9f
|
||||
certbot==0.8.1 \
|
||||
--hash=sha256:89805d9f70249ae859ec4d7a99c00b4bb7083ca90cd12d4d202b76dfc284f7c5 \
|
||||
--hash=sha256:6ca8df3d310ced6687d38aac17c0fb8c1b2ec7a3bea156a254e4cc2a1c132771
|
||||
certbot-apache==0.8.1 \
|
||||
--hash=sha256:c9e3fdc15e65589c2e39eb0e6b1f61f0c0a1db3c17b00bb337f0ff636cc61cb3 \
|
||||
--hash=sha256:0faf2879884d3b7a58b071902fba37d4b8b58a50e2c3b8ac262c0a74134045ed
|
||||
|
||||
UNLIKELY_EOF
|
||||
# -------------------------------------------------------------------------
|
||||
|
|
@ -926,8 +932,10 @@ UNLIKELY_EOF
|
|||
fi
|
||||
if [ -n "$SUDO" ]; then
|
||||
# SUDO is su wrapper or sudo
|
||||
echo "Requesting root privileges to run certbot..."
|
||||
echo " $VENV_BIN/letsencrypt" "$@"
|
||||
if [ "$QUIET" != 1 ]; then
|
||||
echo "Requesting root privileges to run certbot..."
|
||||
echo " $VENV_BIN/letsencrypt" "$@"
|
||||
fi
|
||||
fi
|
||||
if [ -z "$SUDO_ENV" ] ; then
|
||||
# SUDO is su wrapper / noop
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -34,6 +34,7 @@ Help for certbot itself cannot be provided until it is installed.
|
|||
-n, --non-interactive, --noninteractive run without asking for user input
|
||||
--no-self-upgrade do not download updates
|
||||
--os-packages-only install OS dependencies and exit
|
||||
-q, --quiet provide only update/error output
|
||||
-v, --verbose provide more output
|
||||
|
||||
All arguments are accepted and forwarded to the Certbot client when run."
|
||||
|
|
@ -52,15 +53,19 @@ for arg in "$@" ; do
|
|||
HELP=1;;
|
||||
--noninteractive|--non-interactive)
|
||||
ASSUME_YES=1;;
|
||||
--quiet)
|
||||
QUIET=1;;
|
||||
--verbose)
|
||||
VERBOSE=1;;
|
||||
-[!-]*)
|
||||
while getopts ":hnv" short_arg $arg; do
|
||||
while getopts ":hnvq" short_arg $arg; do
|
||||
case "$short_arg" in
|
||||
h)
|
||||
HELP=1;;
|
||||
n)
|
||||
ASSUME_YES=1;;
|
||||
q)
|
||||
QUIET=1;;
|
||||
v)
|
||||
VERBOSE=1;;
|
||||
esac
|
||||
|
|
@ -262,8 +267,10 @@ UNLIKELY_EOF
|
|||
fi
|
||||
if [ -n "$SUDO" ]; then
|
||||
# SUDO is su wrapper or sudo
|
||||
echo "Requesting root privileges to run certbot..."
|
||||
echo " $VENV_BIN/letsencrypt" "$@"
|
||||
if [ "$QUIET" != 1 ]; then
|
||||
echo "Requesting root privileges to run certbot..."
|
||||
echo " $VENV_BIN/letsencrypt" "$@"
|
||||
fi
|
||||
fi
|
||||
if [ -z "$SUDO_ENV" ] ; then
|
||||
# SUDO is su wrapper / noop
|
||||
|
|
|
|||
|
|
@ -32,28 +32,29 @@ ConfigArgParse==0.10.0 \
|
|||
--hash=sha256:3b50a83dd58149dfcee98cb6565265d10b53e9c0a2bca7eeef7fb5f5524890a7
|
||||
configobj==5.0.6 \
|
||||
--hash=sha256:a2f5650770e1c87fb335af19a9b7eb73fc05ccf22144eb68db7d00cd2bcb0902
|
||||
cryptography==1.2.3 \
|
||||
--hash=sha256:031938f73a5c5eb3e809e18ff7caeb6865351871417be6050cb8c86a9a202b9a \
|
||||
--hash=sha256:a179a38d50f8d68b491d7a313db78f8cabe290842cecddddc7b34d408e59db0a \
|
||||
--hash=sha256:906c88b2aadcf99cfabb24098263d1bf65ab0c8688acde10dae1f09d865920f1 \
|
||||
--hash=sha256:6e706c5c6088770b1d1b634e959e21963e315b0255f5f4777125ad3d54082977 \
|
||||
--hash=sha256:f5ebf8e31c48f8707921dca0e994de77813a9c9b9bf03c119c5ddf97bdcffe73 \
|
||||
--hash=sha256:c7b89e42288cc7fbee3812e99ef5c744f22452e11d6822f6807afc6d6b3be83e \
|
||||
--hash=sha256:8408d29865947109d8b68f1837a7cde1aa4dc86e0f79ca3ba58c0c44e443d6a5 \
|
||||
--hash=sha256:c7e76cf3c3d925dd31fa238cfb806cffba718c0f08707d77a538768477969956 \
|
||||
--hash=sha256:7d8de35380f31702758b7753bb5c40723832c73006dedb2f9099bf61a37f7287 \
|
||||
--hash=sha256:5edbee71fae5469ee83fe0a37866b9398c8ce3a46325c24fcedfbf097bb48a19 \
|
||||
--hash=sha256:594edafe4801c13bdc1cc305e7704a90c19617e95936f6ab457ee4ffe000ba50 \
|
||||
--hash=sha256:b7fdb16a0a7f481be42da744bfe1ea2163025de21f90f2c688a316f3c354da9c \
|
||||
--hash=sha256:207b8bf0fe0907336df38b733b487521cf9e138189aba9234ad54fe545dd0db8 \
|
||||
--hash=sha256:509a2f05386270cf783993c90d49ffefb3dd62aee45bf1ea8ce3d2cde7271c21 \
|
||||
--hash=sha256:ac69b65dd1af0179ede40c9f15788c88f73e628ea6c0519de3838e279bb388c6 \
|
||||
--hash=sha256:8df6fad6c6ae12fd7004ea29357f0a2b4d3774eaeca7656530d08d2d90cd41aa \
|
||||
--hash=sha256:0b8b96dd81cc1533a04f30382c0fe21c1972e189f794d0c4261a18cec08fd9b5 \
|
||||
--hash=sha256:cae8fca1883f23c50ea78d89de6fe4fefdb4cea83177760f47177559414ded93 \
|
||||
--hash=sha256:1a471ca576a9cdce1b1cd9f3a22b1d09ee44d46862037557de17919c0db44425 \
|
||||
--hash=sha256:8ec4e8e3d453b3a1b63b5f57737a434dcf1ee4a2f26f6ff7c5a37c3f679104d2 \
|
||||
--hash=sha256:8eb11c77dd8e73f48df6b2f7a7e16173fe0fe8fdfe266232832e88477e08454e
|
||||
cryptography==1.3.4 \
|
||||
--hash=sha256:bede00edd11a2a62c8c98c271cc103fa3a3d72acf64f6e5e4eaf251128897b17 \
|
||||
--hash=sha256:53b39e687b744bb548a98f40736cc529d9f60959b4e6cc551322cf9505d35eb3 \
|
||||
--hash=sha256:474b73ad1139b4e423e46bbd818efd0d5c0df1c65d9f7c957d64c9215d77afde \
|
||||
--hash=sha256:aaddf9592d5b99e32dd518bb4a25b147c124f9d6b4ad64b94f01b15d1666b8c8 \
|
||||
--hash=sha256:6dcad2f407db8c3cd6ecd78361439c449a4f94786b46c54507e7e68f51e1709d \
|
||||
--hash=sha256:475c153fc622e656f1f10a9c9941d0ac7ab18df7c38d35d563a437c1c0e34f24 \
|
||||
--hash=sha256:86dd61df581cba04e89e45081efbc531faff1c9d99c77b1ce97f87216c356353 \
|
||||
--hash=sha256:75cc697e4ef5fdd0102ca749114c6370dbd11db0c9132a18834858c2566247e3 \
|
||||
--hash=sha256:ea03ad5b9df6d79fc9fc1ab23729e01e1c920d2974c5e3c634ccf45a5c378452 \
|
||||
--hash=sha256:c8872b8fe4f3416d6338ab99612f49ab314f7856cb43bffab2a32d28a6267be8 \
|
||||
--hash=sha256:468fc6e16eaec6ceaa6bc341273e6e9912d01b42b740f8cf896ace7fcd6a321d \
|
||||
--hash=sha256:d6fea3c6502735011c5d61a62aef1c1d770fc6a2def45d9e6c0d94c9651e3317 \
|
||||
--hash=sha256:3cf95f179f4bead3d5649b91860ef4cf60ad4244209190fc405908272576d961 \
|
||||
--hash=sha256:141f77e60a5b9158309b2b60288c7f81d37faa15c22a69b94c190ceefaaa6236 \
|
||||
--hash=sha256:87b7a1fe703c6424451f3372d1879dae91c7fe5e13375441a72833db76fee30e \
|
||||
--hash=sha256:f5ee3cb0cf1a6550bf483ccffa6608db267a377b45f7e3a8201a86d1d8feb19f \
|
||||
--hash=sha256:4e097286651ea318300af3251375d48b71b8228481c56cd617ddd4459a1ff261 \
|
||||
--hash=sha256:1e3d3ae3f22f22d50d340f47f25227511326f3f1396c6d2446a5b45b516c4313 \
|
||||
--hash=sha256:6a057941cb64d79834ea3cf99093fcc4787c2a5d44f686c4f297361ddc419bcd \
|
||||
--hash=sha256:68b3d5390b92559ddd3353c73ab2dfcff758f9c4ec4f5d5226ccede0e5d779f4 \
|
||||
--hash=sha256:545dc003b4b6081f9c3e452da15d819b04b696f49484aff64c0a2aedf766bef8 \
|
||||
--hash=sha256:423ff890c01be7c70dbfeaa967eeef5146f1a43a5f810ffdc07b178e48a105a9
|
||||
enum34==1.1.2 \
|
||||
--hash=sha256:2475d7fcddf5951e92ff546972758802de5260bf409319a9f1934e6bbc8b1dc7 \
|
||||
--hash=sha256:35907defb0f992b75ab7788f65fedc1cf20ffa22688e0e6f6f12afc06b3ea501
|
||||
|
|
@ -113,9 +114,9 @@ pyasn1==0.1.9 \
|
|||
--hash=sha256:5191ff6b9126d2c039dd87f8ff025bed274baf07fa78afa46f556b1ad7265d6e \
|
||||
--hash=sha256:8323e03637b2d072cc7041300bac6ec448c3c28950ab40376036788e9a1af629 \
|
||||
--hash=sha256:853cacd96d1f701ddd67aa03ecc05f51890135b7262e922710112f12a2ed2a7f
|
||||
pyOpenSSL==0.15.1 \
|
||||
--hash=sha256:88e45e6bb25dfed272a1ef2e728461d44b634c2cd689e989b6e56a349c5a3ae5 \
|
||||
--hash=sha256:f0a26070d6db0881de8bcc7846934b7c3c930d8f9c79d45883ee48984bc0d672
|
||||
pyopenssl==16.0.0 \
|
||||
--hash=sha256:5add70cf00273bf957ca31fdb0df9b0ae4639e081897d5f86a0ae1f104901230 \
|
||||
--hash=sha256:363d10ee43d062285facf4e465f4f5163f9f702f9134f0a5896f134cbb92d17d
|
||||
pyRFC3339==1.0 \
|
||||
--hash=sha256:eea31835c56e2096af4363a5745a784878a61d043e247d3a6d6a0a32a9741f56 \
|
||||
--hash=sha256:8dfbc6c458b8daba1c0f3620a8c78008b323a268b27b7359e92a4ae41325f535
|
||||
|
|
@ -181,12 +182,12 @@ letsencrypt==0.7.0 \
|
|||
|
||||
# THE LINES BELOW ARE EDITED BY THE RELEASE SCRIPT; ADD ALL DEPENDENCIES ABOVE.
|
||||
|
||||
acme==0.8.0 \
|
||||
--hash=sha256:8561d590e496afb41a8ff2dac389199661d9cd785b1636ae08325771511189af \
|
||||
--hash=sha256:dfa86b547628b231f275c7e0efc7a09bec5dfaec866f89f5c5b59b78c14564da
|
||||
certbot==0.8.0 \
|
||||
--hash=sha256:395c5840ff6b75aa51ee6449c86d016c14c5f65a71281e7bcef5feecac6a3293 \
|
||||
--hash=sha256:3c3c70b484fb3243a166515adc81ae0401c5d687a2763c75b40df9d8241a4314
|
||||
certbot-apache==0.8.0 \
|
||||
--hash=sha256:f4d4fc962ecc19646f6745d49c62a265d26e5b2df3acf34ef4865351594156e3 \
|
||||
--hash=sha256:cfb211debbcb0d0645c88d7e8bb38c591fca263bfdb5337242c023956055e268
|
||||
acme==0.8.1 \
|
||||
--hash=sha256:ccd7883772efbf933f91713b8241455993834f3620c8fbd459d9ed5e50bbaaca \
|
||||
--hash=sha256:d3ea4acf280bf6253ad7d641cb0970f230a19805acfed809e7a8ddcf62157d9f
|
||||
certbot==0.8.1 \
|
||||
--hash=sha256:89805d9f70249ae859ec4d7a99c00b4bb7083ca90cd12d4d202b76dfc284f7c5 \
|
||||
--hash=sha256:6ca8df3d310ced6687d38aac17c0fb8c1b2ec7a3bea156a254e4cc2a1c132771
|
||||
certbot-apache==0.8.1 \
|
||||
--hash=sha256:c9e3fdc15e65589c2e39eb0e6b1f61f0c0a1db3c17b00bb337f0ff636cc61cb3 \
|
||||
--hash=sha256:0faf2879884d3b7a58b071902fba37d4b8b58a50e2c3b8ac262c0a74134045ed
|
||||
|
|
|
|||
1
setup.py
1
setup.py
|
|
@ -69,7 +69,6 @@ dev_extras = [
|
|||
'astroid==1.3.5',
|
||||
'coverage',
|
||||
'nose',
|
||||
'nosexcover',
|
||||
'pep8',
|
||||
'pylint==1.4.2', # upstream #248
|
||||
'tox',
|
||||
|
|
|
|||
2
tox.ini
2
tox.ini
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
[tox]
|
||||
skipsdist = true
|
||||
envlist = py{26,27,33,34,35},py{26,27}-oldest,cover,lint
|
||||
envlist = py{26,33,34,35},cover,lint
|
||||
|
||||
# nosetest -v => more verbose output, allows to detect busy waiting
|
||||
# loops, especially on Travis
|
||||
|
|
|
|||
Loading…
Reference in a new issue