mirror of
https://github.com/certbot/certbot.git
synced 2026-01-24 19:22:07 +03:00
Merge remote-tracking branch 'upstream/master' into bugfix_skeleton
This commit is contained in:
@@ -22,11 +22,19 @@ env:
|
||||
matrix:
|
||||
- TOXENV=py26 BOULDER_INTEGRATION=1
|
||||
- TOXENV=py27 BOULDER_INTEGRATION=1
|
||||
- TOXENV=py26-oldest BOULDER_INTEGRATION=1
|
||||
- TOXENV=py27-oldest BOULDER_INTEGRATION=1
|
||||
- TOXENV=py33
|
||||
- TOXENV=py34
|
||||
- TOXENV=lint
|
||||
- TOXENV=cover
|
||||
# Disabled for now due to requiring sudo -> causing more boulder integration
|
||||
# DNS timeouts :(
|
||||
# - TOXENV=apacheconftest
|
||||
matrix:
|
||||
include:
|
||||
- env: TOXENV=py35
|
||||
python: 3.5
|
||||
|
||||
|
||||
# Only build pushes to the master branch, PRs, and branches beginning with
|
||||
|
||||
383
acme/.pylintrc
Normal file
383
acme/.pylintrc
Normal file
@@ -0,0 +1,383 @@
|
||||
[MASTER]
|
||||
|
||||
# Specify a configuration file.
|
||||
#rcfile=
|
||||
|
||||
# Python code to execute, usually for sys.path manipulation such as
|
||||
# pygtk.require().
|
||||
#init-hook=
|
||||
|
||||
# Profiled execution.
|
||||
profile=no
|
||||
|
||||
# Add files or directories to the blacklist. They should be base names, not
|
||||
# paths.
|
||||
ignore=CVS
|
||||
|
||||
# Pickle collected data for later comparisons.
|
||||
persistent=yes
|
||||
|
||||
# List of plugins (as comma separated values of python modules names) to load,
|
||||
# usually to register additional checkers.
|
||||
load-plugins=linter_plugin
|
||||
|
||||
# DEPRECATED
|
||||
include-ids=no
|
||||
|
||||
# DEPRECATED
|
||||
symbols=no
|
||||
|
||||
# Use multiple processes to speed up Pylint.
|
||||
jobs=1
|
||||
|
||||
# Allow loading of arbitrary C extensions. Extensions are imported into the
|
||||
# active Python interpreter and may run arbitrary code.
|
||||
unsafe-load-any-extension=no
|
||||
|
||||
# A comma-separated list of package or module names from where C extensions may
|
||||
# be loaded. Extensions are loading into the active Python interpreter and may
|
||||
# run arbitrary code
|
||||
extension-pkg-whitelist=
|
||||
|
||||
|
||||
[MESSAGES CONTROL]
|
||||
|
||||
# Only show warnings with the listed confidence levels. Leave empty to show
|
||||
# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED
|
||||
confidence=
|
||||
|
||||
# Enable the message, report, category or checker with the given id(s). You can
|
||||
# either give multiple identifier separated by comma (,) or put this option
|
||||
# multiple time. See also the "--disable" option for examples.
|
||||
#enable=
|
||||
|
||||
# Disable the message, report, category or checker with the given id(s). You
|
||||
# can either give multiple identifiers separated by comma (,) or put this
|
||||
# option multiple times (only on the command line, not in the configuration
|
||||
# file where it should appear only once).You can also use "--disable=all" to
|
||||
# disable everything first and then reenable specific checks. For example, if
|
||||
# you want to run only the similarities checker, you can use "--disable=all
|
||||
# --enable=similarities". If you want to run only the classes checker, but have
|
||||
# no Warning level messages displayed, use"--disable=all --enable=classes
|
||||
# --disable=W"
|
||||
disable=fixme,locally-disabled,abstract-class-not-used
|
||||
# bstract-class-not-used cannot be disabled locally (at least in
|
||||
# pylint 1.4.1/2)
|
||||
|
||||
|
||||
|
||||
[REPORTS]
|
||||
|
||||
# Set the output format. Available formats are text, parseable, colorized, msvs
|
||||
# (visual studio) and html. You can also give a reporter class, eg
|
||||
# mypackage.mymodule.MyReporterClass.
|
||||
output-format=text
|
||||
|
||||
# Put messages in a separate file for each module / package specified on the
|
||||
# command line instead of printing them on stdout. Reports (if any) will be
|
||||
# written in a file name "pylint_global.[txt|html]".
|
||||
files-output=no
|
||||
|
||||
# Tells whether to display a full report or only the messages
|
||||
reports=yes
|
||||
|
||||
# Python expression which should return a note less than 10 (10 is the highest
|
||||
# note). You have access to the variables errors warning, statement which
|
||||
# respectively contain the number of errors / warnings messages and the total
|
||||
# number of statements analyzed. This is used by the global evaluation report
|
||||
# (RP0004).
|
||||
evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
|
||||
|
||||
# Add a comment according to your evaluation note. This is used by the global
|
||||
# evaluation report (RP0004).
|
||||
comment=no
|
||||
|
||||
# Template used to display messages. This is a python new-style format string
|
||||
# used to format the message information. See doc for all details
|
||||
#msg-template=
|
||||
|
||||
|
||||
[FORMAT]
|
||||
|
||||
# Maximum number of characters on a single line.
|
||||
max-line-length=100
|
||||
|
||||
# Regexp for a line that is allowed to be longer than the limit.
|
||||
ignore-long-lines=^\s*(# )?<?https?://\S+>?$
|
||||
|
||||
# Allow the body of an if to be on the same line as the test if there is no
|
||||
# else.
|
||||
single-line-if-stmt=no
|
||||
|
||||
# List of optional constructs for which whitespace checking is disabled
|
||||
no-space-check=trailing-comma,dict-separator
|
||||
|
||||
# Maximum number of lines in a module
|
||||
max-module-lines=1000
|
||||
|
||||
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
|
||||
# tab).
|
||||
indent-string=' '
|
||||
|
||||
# Number of spaces of indent required inside a hanging or continued line.
|
||||
indent-after-paren=4
|
||||
|
||||
# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
|
||||
expected-line-ending-format=
|
||||
|
||||
|
||||
[MISCELLANEOUS]
|
||||
|
||||
# List of note tags to take in consideration, separated by a comma.
|
||||
notes=FIXME,XXX,TODO
|
||||
|
||||
|
||||
[LOGGING]
|
||||
|
||||
# Logging modules to check that the string format arguments are in logging
|
||||
# function parameter format
|
||||
logging-modules=logging,logger
|
||||
|
||||
|
||||
[SPELLING]
|
||||
|
||||
# Spelling dictionary name. Available dictionaries: none. To make it working
|
||||
# install python-enchant package.
|
||||
spelling-dict=
|
||||
|
||||
# List of comma separated words that should not be checked.
|
||||
spelling-ignore-words=
|
||||
|
||||
# A path to a file that contains private dictionary; one word per line.
|
||||
spelling-private-dict-file=
|
||||
|
||||
# Tells whether to store unknown words to indicated private dictionary in
|
||||
# --spelling-private-dict-file option instead of raising a message.
|
||||
spelling-store-unknown-words=no
|
||||
|
||||
|
||||
[TYPECHECK]
|
||||
|
||||
# Tells whether missing members accessed in mixin class should be ignored. A
|
||||
# mixin class is detected if its name ends with "mixin" (case insensitive).
|
||||
ignore-mixin-members=yes
|
||||
|
||||
# List of module names for which member attributes should not be checked
|
||||
# (useful for modules/projects where namespaces are manipulated during runtime
|
||||
# and thus existing member attributes cannot be deduced by static analysis
|
||||
ignored-modules=
|
||||
|
||||
# List of classes names for which member attributes should not be checked
|
||||
# (useful for classes with attributes dynamically set).
|
||||
ignored-classes=SQLObject
|
||||
|
||||
# When zope mode is activated, add a predefined set of Zope acquired attributes
|
||||
# to generated-members.
|
||||
zope=no
|
||||
|
||||
# List of members which are set dynamically and missed by pylint inference
|
||||
# system, and so shouldn't trigger E0201 when accessed. Python regular
|
||||
# expressions are accepted.
|
||||
generated-members=REQUEST,acl_users,aq_parent
|
||||
|
||||
|
||||
[SIMILARITIES]
|
||||
|
||||
# Minimum lines number of a similarity.
|
||||
min-similarity-lines=4
|
||||
|
||||
# Ignore comments when computing similarities.
|
||||
ignore-comments=yes
|
||||
|
||||
# Ignore docstrings when computing similarities.
|
||||
ignore-docstrings=yes
|
||||
|
||||
# Ignore imports when computing similarities.
|
||||
ignore-imports=no
|
||||
|
||||
|
||||
[VARIABLES]
|
||||
|
||||
# Tells whether we should check for unused import in __init__ files.
|
||||
init-import=no
|
||||
|
||||
# A regular expression matching the name of dummy variables (i.e. expectedly
|
||||
# not used).
|
||||
dummy-variables-rgx=_$|dummy|unused
|
||||
|
||||
# List of additional names supposed to be defined in builtins. Remember that
|
||||
# you should avoid to define new builtins when possible.
|
||||
additional-builtins=
|
||||
|
||||
# List of strings which can identify a callback function by name. A callback
|
||||
# name must start or end with one of those strings.
|
||||
callbacks=cb_,_cb
|
||||
|
||||
|
||||
[BASIC]
|
||||
|
||||
# Required attributes for module, separated by a comma
|
||||
required-attributes=
|
||||
|
||||
# List of builtins function names that should not be used, separated by a comma
|
||||
bad-functions=map,filter,input
|
||||
|
||||
# Good variable names which should always be accepted, separated by a comma
|
||||
good-names=i,j,k,ex,Run,_,logger
|
||||
|
||||
# Bad variable names which should always be refused, separated by a comma
|
||||
bad-names=foo,bar,baz,toto,tutu,tata
|
||||
|
||||
# Colon-delimited sets of names that determine each other's naming style when
|
||||
# the name regexes allow several styles.
|
||||
name-group=
|
||||
|
||||
# Include a hint for the correct naming format with invalid-name
|
||||
include-naming-hint=no
|
||||
|
||||
# Regular expression matching correct function names
|
||||
function-rgx=[a-z_][a-z0-9_]{2,40}$
|
||||
|
||||
# Naming hint for function names
|
||||
function-name-hint=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
# Regular expression matching correct variable names
|
||||
variable-rgx=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
# Naming hint for variable names
|
||||
variable-name-hint=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
# Regular expression matching correct constant names
|
||||
const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$
|
||||
|
||||
# Naming hint for constant names
|
||||
const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$
|
||||
|
||||
# Regular expression matching correct attribute names
|
||||
attr-rgx=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
# Naming hint for attribute names
|
||||
attr-name-hint=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
# Regular expression matching correct argument names
|
||||
argument-rgx=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
# Naming hint for argument names
|
||||
argument-name-hint=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
# Regular expression matching correct class attribute names
|
||||
class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
|
||||
|
||||
# Naming hint for class attribute names
|
||||
class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
|
||||
|
||||
# Regular expression matching correct inline iteration names
|
||||
inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
|
||||
|
||||
# Naming hint for inline iteration names
|
||||
inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$
|
||||
|
||||
# Regular expression matching correct class names
|
||||
class-rgx=[A-Z_][a-zA-Z0-9]+$
|
||||
|
||||
# Naming hint for class names
|
||||
class-name-hint=[A-Z_][a-zA-Z0-9]+$
|
||||
|
||||
# Regular expression matching correct module names
|
||||
module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
|
||||
|
||||
# Naming hint for module names
|
||||
module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
|
||||
|
||||
# Regular expression matching correct method names
|
||||
method-rgx=[a-z_][a-z0-9_]{2,49}$
|
||||
|
||||
# Naming hint for method names
|
||||
method-name-hint=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
# Regular expression which should only match function or class names that do
|
||||
# not require a docstring.
|
||||
no-docstring-rgx=__.*__|test_[A-Za-z0-9_]*|_.*|.*Test
|
||||
|
||||
# Minimum line length for functions/classes that require docstrings, shorter
|
||||
# ones are exempt.
|
||||
docstring-min-length=-1
|
||||
|
||||
|
||||
[CLASSES]
|
||||
|
||||
# List of interface methods to ignore, separated by a comma. This is used for
|
||||
# instance to not check methods defines in Zope's Interface base class.
|
||||
ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by
|
||||
|
||||
# List of method names used to declare (i.e. assign) instance attributes.
|
||||
defining-attr-methods=__init__,__new__,setUp
|
||||
|
||||
# List of valid names for the first argument in a class method.
|
||||
valid-classmethod-first-arg=cls
|
||||
|
||||
# List of valid names for the first argument in a metaclass class method.
|
||||
valid-metaclass-classmethod-first-arg=mcs
|
||||
|
||||
# List of member names, which should be excluded from the protected access
|
||||
# warning.
|
||||
exclude-protected=_asdict,_fields,_replace,_source,_make
|
||||
|
||||
|
||||
[DESIGN]
|
||||
|
||||
# Maximum number of arguments for function / method
|
||||
max-args=6
|
||||
|
||||
# Argument names that match this expression will be ignored. Default to name
|
||||
# with leading underscore
|
||||
ignored-argument-names=_.*
|
||||
|
||||
# Maximum number of locals for function / method body
|
||||
max-locals=15
|
||||
|
||||
# Maximum number of return / yield for function / method body
|
||||
max-returns=6
|
||||
|
||||
# Maximum number of branch for function / method body
|
||||
max-branches=12
|
||||
|
||||
# Maximum number of statements in function / method body
|
||||
max-statements=50
|
||||
|
||||
# Maximum number of parents for a class (see R0901).
|
||||
max-parents=12
|
||||
|
||||
# Maximum number of attributes for a class (see R0902).
|
||||
max-attributes=7
|
||||
|
||||
# Minimum number of public methods for a class (see R0903).
|
||||
min-public-methods=2
|
||||
|
||||
# Maximum number of public methods for a class (see R0904).
|
||||
max-public-methods=20
|
||||
|
||||
|
||||
[IMPORTS]
|
||||
|
||||
# Deprecated modules which should not be used, separated by a comma
|
||||
deprecated-modules=regsub,TERMIOS,Bastion,rexec
|
||||
|
||||
# Create a graph of every (i.e. internal and external) dependencies in the
|
||||
# given file (report RP0402 must not be disabled)
|
||||
import-graph=
|
||||
|
||||
# Create a graph of external dependencies in the given file (report RP0402 must
|
||||
# not be disabled)
|
||||
ext-import-graph=
|
||||
|
||||
# Create a graph of internal dependencies in the given file (report RP0402 must
|
||||
# not be disabled)
|
||||
int-import-graph=
|
||||
|
||||
|
||||
[EXCEPTIONS]
|
||||
|
||||
# Exceptions that will emit a warning when being caught. Defaults to
|
||||
# "Exception"
|
||||
overgeneral-exceptions=Exception
|
||||
@@ -336,7 +336,7 @@ class TLSSNI01Response(KeyAuthorizationChallengeResponse):
|
||||
"""
|
||||
|
||||
@property
|
||||
def z(self):
|
||||
def z(self): # pylint: disable=invalid-name
|
||||
"""``z`` value used for verification.
|
||||
|
||||
:rtype bytes:
|
||||
@@ -391,7 +391,14 @@ class TLSSNI01Response(KeyAuthorizationChallengeResponse):
|
||||
return crypto_util.probe_sni(**kwargs)
|
||||
|
||||
def verify_cert(self, cert):
|
||||
"""Verify tls-sni-01 challenge certificate."""
|
||||
"""Verify tls-sni-01 challenge certificate.
|
||||
|
||||
:param OpensSSL.crypto.X509 cert: Challenge certificate.
|
||||
|
||||
:returns: Whether the certificate was successfully verified.
|
||||
:rtype: bool
|
||||
|
||||
"""
|
||||
# pylint: disable=protected-access
|
||||
sans = crypto_util._pyopenssl_cert_or_req_san(cert)
|
||||
logging.debug('Certificate %s. SANs: %s', cert.digest('sha1'), sans)
|
||||
|
||||
@@ -13,7 +13,7 @@ from acme import other
|
||||
from acme import test_util
|
||||
|
||||
|
||||
CERT = test_util.load_cert('cert.pem')
|
||||
CERT = test_util.load_comparable_cert('cert.pem')
|
||||
KEY = jose.JWKRSA(key=test_util.load_rsa_private_key('rsa512_key.pem'))
|
||||
|
||||
|
||||
@@ -421,7 +421,7 @@ class ProofOfPossessionHintsTest(unittest.TestCase):
|
||||
'jwk': jwk,
|
||||
'certFingerprints': cert_fingerprints,
|
||||
'certs': (jose.encode_b64jose(OpenSSL.crypto.dump_certificate(
|
||||
OpenSSL.crypto.FILETYPE_ASN1, CERT)),),
|
||||
OpenSSL.crypto.FILETYPE_ASN1, CERT.wrapped)),),
|
||||
'subjectKeyIdentifiers': subject_key_identifiers,
|
||||
'serialNumbers': serial_numbers,
|
||||
'issuers': issuers,
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
"""Crypto utilities."""
|
||||
import contextlib
|
||||
import logging
|
||||
import re
|
||||
import socket
|
||||
import sys
|
||||
|
||||
from six.moves import range # pylint: disable=import-error,redefined-builtin
|
||||
|
||||
import OpenSSL
|
||||
|
||||
from acme import errors
|
||||
@@ -70,7 +69,7 @@ class SSLSocket(object): # pylint: disable=too-few-public-methods
|
||||
class FakeConnection(object):
|
||||
"""Fake OpenSSL.SSL.Connection."""
|
||||
|
||||
# pylint: disable=missing-docstring
|
||||
# pylint: disable=too-few-public-methods,missing-docstring
|
||||
|
||||
def __init__(self, connection):
|
||||
self._wrapped = connection
|
||||
@@ -161,31 +160,31 @@ def _pyopenssl_cert_or_req_san(cert_or_req):
|
||||
:rtype: `list` of `unicode`
|
||||
|
||||
"""
|
||||
# constants based on implementation of
|
||||
# OpenSSL.crypto.X509Error._subjectAltNameString
|
||||
parts_separator = ", "
|
||||
# This function finds SANs by dumping the certificate/CSR to text and
|
||||
# searching for "X509v3 Subject Alternative Name" in the text. This method
|
||||
# is used to support PyOpenSSL version 0.13 where the
|
||||
# `_subjectAltNameString` and `get_extensions` methods are not available
|
||||
# for CSRs.
|
||||
|
||||
# constants based on PyOpenSSL certificate/CSR text dump
|
||||
part_separator = ":"
|
||||
extension_short_name = b"subjectAltName"
|
||||
parts_separator = ", "
|
||||
prefix = "DNS" + part_separator
|
||||
|
||||
if hasattr(cert_or_req, 'get_extensions'): # X509Req
|
||||
extensions = cert_or_req.get_extensions()
|
||||
else: # X509
|
||||
extensions = [cert_or_req.get_extension(i)
|
||||
for i in range(cert_or_req.get_extension_count())]
|
||||
|
||||
# pylint: disable=protected-access,no-member
|
||||
label = OpenSSL.crypto.X509Extension._prefixes[OpenSSL.crypto._lib.GEN_DNS]
|
||||
assert parts_separator not in label
|
||||
prefix = label + part_separator
|
||||
|
||||
san_extensions = [
|
||||
ext._subjectAltNameString().split(parts_separator)
|
||||
for ext in extensions if ext.get_short_name() == extension_short_name]
|
||||
if isinstance(cert_or_req, OpenSSL.crypto.X509):
|
||||
func = OpenSSL.crypto.dump_certificate
|
||||
else:
|
||||
func = OpenSSL.crypto.dump_certificate_request
|
||||
text = func(OpenSSL.crypto.FILETYPE_TEXT, cert_or_req).decode("utf-8")
|
||||
# WARNING: this function does not support multiple SANs extensions.
|
||||
# Multiple X509v3 extensions of the same type is disallowed by RFC 5280.
|
||||
match = re.search(r"X509v3 Subject Alternative Name:\s*(.*)", text)
|
||||
# WARNING: this function assumes that no SAN can include
|
||||
# parts_separator, hence the split!
|
||||
sans_parts = [] if match is None else match.group(1).split(parts_separator)
|
||||
|
||||
return [part.split(part_separator)[1] for parts in san_extensions
|
||||
for part in parts if part.startswith(prefix)]
|
||||
return [part.split(part_separator)[1]
|
||||
for part in sans_parts if part.startswith(prefix)]
|
||||
|
||||
|
||||
def gen_ss_cert(key, domains, not_before=None,
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
"""Tests for acme.crypto_util."""
|
||||
import itertools
|
||||
import socket
|
||||
import threading
|
||||
import time
|
||||
import unittest
|
||||
|
||||
import six
|
||||
from six.moves import socketserver # pylint: disable=import-error
|
||||
|
||||
from acme import errors
|
||||
@@ -15,10 +17,10 @@ class SSLSocketAndProbeSNITest(unittest.TestCase):
|
||||
"""Tests for acme.crypto_util.SSLSocket/probe_sni."""
|
||||
|
||||
def setUp(self):
|
||||
self.cert = test_util.load_cert('cert.pem')
|
||||
self.cert = test_util.load_comparable_cert('cert.pem')
|
||||
key = test_util.load_pyopenssl_private_key('rsa512_key.pem')
|
||||
# pylint: disable=protected-access
|
||||
certs = {b'foo': (key, self.cert._wrapped)}
|
||||
certs = {b'foo': (key, self.cert.wrapped)}
|
||||
|
||||
from acme.crypto_util import SSLSocket
|
||||
|
||||
@@ -69,6 +71,15 @@ class PyOpenSSLCertOrReqSANTest(unittest.TestCase):
|
||||
from acme.crypto_util import _pyopenssl_cert_or_req_san
|
||||
return _pyopenssl_cert_or_req_san(loader(name))
|
||||
|
||||
@classmethod
|
||||
def _get_idn_names(cls):
|
||||
"""Returns expected names from '{cert,csr}-idnsans.pem'."""
|
||||
chars = [six.unichr(i) for i in itertools.chain(range(0x3c3, 0x400),
|
||||
range(0x641, 0x6fc),
|
||||
range(0x1820, 0x1877))]
|
||||
return [''.join(chars[i: i + 45]) + '.invalid'
|
||||
for i in range(0, len(chars), 45)]
|
||||
|
||||
def _call_cert(self, name):
|
||||
return self._call(test_util.load_cert, name)
|
||||
|
||||
@@ -82,6 +93,14 @@ class PyOpenSSLCertOrReqSANTest(unittest.TestCase):
|
||||
self.assertEqual(self._call_cert('cert-san.pem'),
|
||||
['example.com', 'www.example.com'])
|
||||
|
||||
def test_cert_hundred_sans(self):
|
||||
self.assertEqual(self._call_cert('cert-100sans.pem'),
|
||||
['example{0}.com'.format(i) for i in range(1, 101)])
|
||||
|
||||
def test_cert_idn_sans(self):
|
||||
self.assertEqual(self._call_cert('cert-idnsans.pem'),
|
||||
self._get_idn_names())
|
||||
|
||||
def test_csr_no_sans(self):
|
||||
self.assertEqual(self._call_csr('csr-nosans.pem'), [])
|
||||
|
||||
@@ -94,10 +113,18 @@ class PyOpenSSLCertOrReqSANTest(unittest.TestCase):
|
||||
|
||||
def test_csr_six_sans(self):
|
||||
self.assertEqual(self._call_csr('csr-6sans.pem'),
|
||||
["example.com", "example.org", "example.net",
|
||||
"example.info", "subdomain.example.com",
|
||||
"other.subdomain.example.com"])
|
||||
['example.com', 'example.org', 'example.net',
|
||||
'example.info', 'subdomain.example.com',
|
||||
'other.subdomain.example.com'])
|
||||
|
||||
def test_csr_hundred_sans(self):
|
||||
self.assertEqual(self._call_csr('csr-100sans.pem'),
|
||||
['example{0}.com'.format(i) for i in range(1, 101)])
|
||||
|
||||
def test_csr_idn_sans(self):
|
||||
self.assertEqual(self._call_csr('csr-idnsans.pem'),
|
||||
self._get_idn_names())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if __name__ == '__main__':
|
||||
unittest.main() # pragma: no cover
|
||||
|
||||
@@ -226,7 +226,7 @@ class JSONObjectWithFields(util.ImmutableMap, interfaces.JSONDeSerializable):
|
||||
|
||||
:param str name: Name of the field to be encoded.
|
||||
|
||||
:raises erors.SerializationError: if field cannot be serialized
|
||||
:raises errors.SerializationError: if field cannot be serialized
|
||||
:raises errors.Error: if field could not be found
|
||||
|
||||
"""
|
||||
@@ -373,7 +373,7 @@ def encode_cert(cert):
|
||||
|
||||
"""
|
||||
return encode_b64jose(OpenSSL.crypto.dump_certificate(
|
||||
OpenSSL.crypto.FILETYPE_ASN1, cert))
|
||||
OpenSSL.crypto.FILETYPE_ASN1, cert.wrapped))
|
||||
|
||||
|
||||
def decode_cert(b64der):
|
||||
@@ -398,7 +398,7 @@ def encode_csr(csr):
|
||||
|
||||
"""
|
||||
return encode_b64jose(OpenSSL.crypto.dump_certificate_request(
|
||||
OpenSSL.crypto.FILETYPE_ASN1, csr))
|
||||
OpenSSL.crypto.FILETYPE_ASN1, csr.wrapped))
|
||||
|
||||
|
||||
def decode_csr(b64der):
|
||||
|
||||
@@ -12,8 +12,8 @@ from acme.jose import interfaces
|
||||
from acme.jose import util
|
||||
|
||||
|
||||
CERT = test_util.load_cert('cert.pem')
|
||||
CSR = test_util.load_csr('csr.pem')
|
||||
CERT = test_util.load_comparable_cert('cert.pem')
|
||||
CSR = test_util.load_comparable_csr('csr.pem')
|
||||
|
||||
|
||||
class FieldTest(unittest.TestCase):
|
||||
|
||||
@@ -124,7 +124,7 @@ class Header(json_util.JSONObjectWithFields):
|
||||
@x5c.encoder
|
||||
def x5c(value): # pylint: disable=missing-docstring,no-self-argument
|
||||
return [base64.b64encode(OpenSSL.crypto.dump_certificate(
|
||||
OpenSSL.crypto.FILETYPE_ASN1, cert)) for cert in value]
|
||||
OpenSSL.crypto.FILETYPE_ASN1, cert.wrapped)) for cert in value]
|
||||
|
||||
@x5c.decoder
|
||||
def x5c(value): # pylint: disable=missing-docstring,no-self-argument
|
||||
|
||||
@@ -13,7 +13,7 @@ from acme.jose import jwa
|
||||
from acme.jose import jwk
|
||||
|
||||
|
||||
CERT = test_util.load_cert('cert.pem')
|
||||
CERT = test_util.load_comparable_cert('cert.pem')
|
||||
KEY = jwk.JWKRSA.load(test_util.load_vector('rsa512_key.pem'))
|
||||
|
||||
|
||||
@@ -68,13 +68,12 @@ class HeaderTest(unittest.TestCase):
|
||||
from acme.jose.jws import Header
|
||||
header = Header(x5c=(CERT, CERT))
|
||||
jobj = header.to_partial_json()
|
||||
cert_b64 = base64.b64encode(OpenSSL.crypto.dump_certificate(
|
||||
OpenSSL.crypto.FILETYPE_ASN1, CERT))
|
||||
cert_asn1 = OpenSSL.crypto.dump_certificate(
|
||||
OpenSSL.crypto.FILETYPE_ASN1, CERT.wrapped)
|
||||
cert_b64 = base64.b64encode(cert_asn1)
|
||||
self.assertEqual(jobj, {'x5c': [cert_b64, cert_b64]})
|
||||
self.assertEqual(header, Header.from_json(jobj))
|
||||
jobj['x5c'][0] = base64.b64encode(
|
||||
b'xxx' + OpenSSL.crypto.dump_certificate(
|
||||
OpenSSL.crypto.FILETYPE_ASN1, CERT))
|
||||
jobj['x5c'][0] = base64.b64encode(b'xxx' + cert_asn1)
|
||||
self.assertRaises(errors.DeserializationError, Header.from_json, jobj)
|
||||
|
||||
def test_find_key(self):
|
||||
|
||||
@@ -29,32 +29,41 @@ class abstractclassmethod(classmethod):
|
||||
class ComparableX509(object): # pylint: disable=too-few-public-methods
|
||||
"""Wrapper for OpenSSL.crypto.X509** objects that supports __eq__.
|
||||
|
||||
Wraps around:
|
||||
|
||||
- :class:`OpenSSL.crypto.X509`
|
||||
- :class:`OpenSSL.crypto.X509Req`
|
||||
:ivar wrapped: Wrapped certificate or certificate request.
|
||||
:type wrapped: `OpenSSL.crypto.X509` or `OpenSSL.crypto.X509Req`.
|
||||
|
||||
"""
|
||||
def __init__(self, wrapped):
|
||||
assert isinstance(wrapped, OpenSSL.crypto.X509) or isinstance(
|
||||
wrapped, OpenSSL.crypto.X509Req)
|
||||
self._wrapped = wrapped
|
||||
self.wrapped = wrapped
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self._wrapped, name)
|
||||
return getattr(self.wrapped, name)
|
||||
|
||||
def _dump(self, filetype=OpenSSL.crypto.FILETYPE_ASN1):
|
||||
# pylint: disable=missing-docstring,protected-access
|
||||
if isinstance(self._wrapped, OpenSSL.crypto.X509):
|
||||
"""Dumps the object into a buffer with the specified encoding.
|
||||
|
||||
:param int filetype: The desired encoding. Should be one of
|
||||
`OpenSSL.crypto.FILETYPE_ASN1`,
|
||||
`OpenSSL.crypto.FILETYPE_PEM`, or
|
||||
`OpenSSL.crypto.FILETYPE_TEXT`.
|
||||
|
||||
:returns: Encoded X509 object.
|
||||
:rtype: str
|
||||
|
||||
"""
|
||||
if isinstance(self.wrapped, OpenSSL.crypto.X509):
|
||||
func = OpenSSL.crypto.dump_certificate
|
||||
else: # assert in __init__ makes sure this is X509Req
|
||||
func = OpenSSL.crypto.dump_certificate_request
|
||||
return func(filetype, self._wrapped)
|
||||
return func(filetype, self.wrapped)
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, self.__class__):
|
||||
return NotImplemented
|
||||
return self._dump() == other._dump() # pylint: disable=protected-access
|
||||
# pylint: disable=protected-access
|
||||
return self._dump() == other._dump()
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self.__class__, self._dump()))
|
||||
@@ -63,7 +72,7 @@ class ComparableX509(object): # pylint: disable=too-few-public-methods
|
||||
return not self == other
|
||||
|
||||
def __repr__(self):
|
||||
return '<{0}({1!r})>'.format(self.__class__.__name__, self._wrapped)
|
||||
return '<{0}({1!r})>'.format(self.__class__.__name__, self.wrapped)
|
||||
|
||||
|
||||
class ComparableKey(object): # pylint: disable=too-few-public-methods
|
||||
@@ -130,7 +139,7 @@ class ImmutableMap(collections.Mapping, collections.Hashable):
|
||||
"""Immutable key to value mapping with attribute access."""
|
||||
|
||||
__slots__ = ()
|
||||
"""Must be overriden in subclasses."""
|
||||
"""Must be overridden in subclasses."""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
if set(kwargs) != set(self.__slots__):
|
||||
|
||||
@@ -11,14 +11,17 @@ class ComparableX509Test(unittest.TestCase):
|
||||
"""Tests for acme.jose.util.ComparableX509."""
|
||||
|
||||
def setUp(self):
|
||||
# test_util.load_{csr,cert} return ComparableX509
|
||||
self.req1 = test_util.load_csr('csr.pem')
|
||||
self.req2 = test_util.load_csr('csr.pem')
|
||||
self.req_other = test_util.load_csr('csr-san.pem')
|
||||
# test_util.load_comparable_{csr,cert} return ComparableX509
|
||||
self.req1 = test_util.load_comparable_csr('csr.pem')
|
||||
self.req2 = test_util.load_comparable_csr('csr.pem')
|
||||
self.req_other = test_util.load_comparable_csr('csr-san.pem')
|
||||
|
||||
self.cert1 = test_util.load_cert('cert.pem')
|
||||
self.cert2 = test_util.load_cert('cert.pem')
|
||||
self.cert_other = test_util.load_cert('cert-san.pem')
|
||||
self.cert1 = test_util.load_comparable_cert('cert.pem')
|
||||
self.cert2 = test_util.load_comparable_cert('cert.pem')
|
||||
self.cert_other = test_util.load_comparable_cert('cert-san.pem')
|
||||
|
||||
def test_getattr_proxy(self):
|
||||
self.assertTrue(self.cert1.has_expired())
|
||||
|
||||
def test_eq(self):
|
||||
self.assertEqual(self.req1, self.req2)
|
||||
@@ -41,8 +44,8 @@ class ComparableX509Test(unittest.TestCase):
|
||||
|
||||
def test_repr(self):
|
||||
for x509 in self.req1, self.cert1:
|
||||
self.assertTrue(repr(x509).startswith(
|
||||
'<ComparableX509(<OpenSSL.crypto.X509'))
|
||||
self.assertEqual(repr(x509),
|
||||
'<ComparableX509({0!r})>'.format(x509.wrapped))
|
||||
|
||||
|
||||
class ComparableRSAKeyTest(unittest.TestCase):
|
||||
|
||||
@@ -23,13 +23,15 @@ class Error(jose.JSONObjectWithFields, errors.Error):
|
||||
('badCSR', 'The CSR is unacceptable (e.g., due to a short key)'),
|
||||
('badNonce', 'The client sent an unacceptable anti-replay nonce'),
|
||||
('connection', 'The server could not connect to the client to '
|
||||
'verify the domain'),
|
||||
'verify the domain'),
|
||||
('dnssec', 'The server could not validate a DNSSEC signed domain'),
|
||||
('invalidEmail',
|
||||
'The provided email for a registration was invalid'),
|
||||
('malformed', 'The request message was malformed'),
|
||||
('rateLimited', 'There were too many requests of a given type'),
|
||||
('serverInternal', 'The server experienced an internal error'),
|
||||
('tls', 'The server experienced a TLS error during domain '
|
||||
'verification'),
|
||||
'verification'),
|
||||
('unauthorized', 'The client lacks sufficient authorization'),
|
||||
('unknownHost', 'The server could not resolve a domain name'),
|
||||
)
|
||||
|
||||
@@ -8,8 +8,8 @@ from acme import jose
|
||||
from acme import test_util
|
||||
|
||||
|
||||
CERT = test_util.load_cert('cert.der')
|
||||
CSR = test_util.load_csr('csr.der')
|
||||
CERT = test_util.load_comparable_cert('cert.der')
|
||||
CSR = test_util.load_comparable_csr('csr.der')
|
||||
KEY = test_util.load_rsa_private_key('rsa512_key.pem')
|
||||
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ class TLSSNI01ServerTest(unittest.TestCase):
|
||||
self.certs = {
|
||||
b'localhost': (test_util.load_pyopenssl_private_key('rsa512_key.pem'),
|
||||
# pylint: disable=protected-access
|
||||
test_util.load_cert('cert.pem')._wrapped),
|
||||
test_util.load_cert('cert.pem')),
|
||||
}
|
||||
from acme.standalone import TLSSNI01Server
|
||||
self.server = TLSSNI01Server(("", 0), certs=self.certs)
|
||||
@@ -146,7 +146,7 @@ class TestSimpleTLSSNI01Server(unittest.TestCase):
|
||||
time.sleep(1) # wait until thread starts
|
||||
else:
|
||||
self.assertEqual(jose.ComparableX509(cert),
|
||||
test_util.load_cert('cert.pem'))
|
||||
test_util.load_comparable_cert('cert.pem'))
|
||||
break
|
||||
|
||||
|
||||
|
||||
@@ -40,16 +40,24 @@ def load_cert(*names):
|
||||
"""Load certificate."""
|
||||
loader = _guess_loader(
|
||||
names[-1], OpenSSL.crypto.FILETYPE_PEM, OpenSSL.crypto.FILETYPE_ASN1)
|
||||
return jose.ComparableX509(OpenSSL.crypto.load_certificate(
|
||||
loader, load_vector(*names)))
|
||||
return OpenSSL.crypto.load_certificate(loader, load_vector(*names))
|
||||
|
||||
|
||||
def load_comparable_cert(*names):
|
||||
"""Load ComparableX509 cert."""
|
||||
return jose.ComparableX509(load_cert(*names))
|
||||
|
||||
|
||||
def load_csr(*names):
|
||||
"""Load certificate request."""
|
||||
loader = _guess_loader(
|
||||
names[-1], OpenSSL.crypto.FILETYPE_PEM, OpenSSL.crypto.FILETYPE_ASN1)
|
||||
return jose.ComparableX509(OpenSSL.crypto.load_certificate_request(
|
||||
loader, load_vector(*names)))
|
||||
return OpenSSL.crypto.load_certificate_request(loader, load_vector(*names))
|
||||
|
||||
|
||||
def load_comparable_csr(*names):
|
||||
"""Load ComparableX509 certificate request."""
|
||||
return jose.ComparableX509(load_csr(*names))
|
||||
|
||||
|
||||
def load_rsa_private_key(*names):
|
||||
|
||||
44
acme/acme/testdata/cert-100sans.pem
vendored
Normal file
44
acme/acme/testdata/cert-100sans.pem
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIHxDCCB26gAwIBAgIJAOGrG1Un9lHiMA0GCSqGSIb3DQEBCwUAMGQxCzAJBgNV
|
||||
BAgMAkNBMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMScwJQYDVQQLDB5FbGVjdHJv
|
||||
bmljIEZyb250aWVyIEZvdW5kYXRpb24xFDASBgNVBAMMC2V4YW1wbGUuY29tMB4X
|
||||
DTE2MDEwNjE5MDkzN1oXDTE2MDEwNzE5MDkzN1owZDELMAkGA1UECAwCQ0ExFjAU
|
||||
BgNVBAcMDVNhbiBGcmFuY2lzY28xJzAlBgNVBAsMHkVsZWN0cm9uaWMgRnJvbnRp
|
||||
ZXIgRm91bmRhdGlvbjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wXDANBgkqhkiG9w0B
|
||||
AQEFAANLADBIAkEArHVztFHtH92ucFJD/N/HW9AsdRsUuHUBBBDlHwNlRd3fp580
|
||||
rv2+6QWE30cWgdmJS86ObRz6lUTor4R0T+3C5QIDAQABo4IGATCCBf0wCQYDVR0T
|
||||
BAIwADALBgNVHQ8EBAMCBeAwggXhBgNVHREEggXYMIIF1IIMZXhhbXBsZTEuY29t
|
||||
ggxleGFtcGxlMi5jb22CDGV4YW1wbGUzLmNvbYIMZXhhbXBsZTQuY29tggxleGFt
|
||||
cGxlNS5jb22CDGV4YW1wbGU2LmNvbYIMZXhhbXBsZTcuY29tggxleGFtcGxlOC5j
|
||||
b22CDGV4YW1wbGU5LmNvbYINZXhhbXBsZTEwLmNvbYINZXhhbXBsZTExLmNvbYIN
|
||||
ZXhhbXBsZTEyLmNvbYINZXhhbXBsZTEzLmNvbYINZXhhbXBsZTE0LmNvbYINZXhh
|
||||
bXBsZTE1LmNvbYINZXhhbXBsZTE2LmNvbYINZXhhbXBsZTE3LmNvbYINZXhhbXBs
|
||||
ZTE4LmNvbYINZXhhbXBsZTE5LmNvbYINZXhhbXBsZTIwLmNvbYINZXhhbXBsZTIx
|
||||
LmNvbYINZXhhbXBsZTIyLmNvbYINZXhhbXBsZTIzLmNvbYINZXhhbXBsZTI0LmNv
|
||||
bYINZXhhbXBsZTI1LmNvbYINZXhhbXBsZTI2LmNvbYINZXhhbXBsZTI3LmNvbYIN
|
||||
ZXhhbXBsZTI4LmNvbYINZXhhbXBsZTI5LmNvbYINZXhhbXBsZTMwLmNvbYINZXhh
|
||||
bXBsZTMxLmNvbYINZXhhbXBsZTMyLmNvbYINZXhhbXBsZTMzLmNvbYINZXhhbXBs
|
||||
ZTM0LmNvbYINZXhhbXBsZTM1LmNvbYINZXhhbXBsZTM2LmNvbYINZXhhbXBsZTM3
|
||||
LmNvbYINZXhhbXBsZTM4LmNvbYINZXhhbXBsZTM5LmNvbYINZXhhbXBsZTQwLmNv
|
||||
bYINZXhhbXBsZTQxLmNvbYINZXhhbXBsZTQyLmNvbYINZXhhbXBsZTQzLmNvbYIN
|
||||
ZXhhbXBsZTQ0LmNvbYINZXhhbXBsZTQ1LmNvbYINZXhhbXBsZTQ2LmNvbYINZXhh
|
||||
bXBsZTQ3LmNvbYINZXhhbXBsZTQ4LmNvbYINZXhhbXBsZTQ5LmNvbYINZXhhbXBs
|
||||
ZTUwLmNvbYINZXhhbXBsZTUxLmNvbYINZXhhbXBsZTUyLmNvbYINZXhhbXBsZTUz
|
||||
LmNvbYINZXhhbXBsZTU0LmNvbYINZXhhbXBsZTU1LmNvbYINZXhhbXBsZTU2LmNv
|
||||
bYINZXhhbXBsZTU3LmNvbYINZXhhbXBsZTU4LmNvbYINZXhhbXBsZTU5LmNvbYIN
|
||||
ZXhhbXBsZTYwLmNvbYINZXhhbXBsZTYxLmNvbYINZXhhbXBsZTYyLmNvbYINZXhh
|
||||
bXBsZTYzLmNvbYINZXhhbXBsZTY0LmNvbYINZXhhbXBsZTY1LmNvbYINZXhhbXBs
|
||||
ZTY2LmNvbYINZXhhbXBsZTY3LmNvbYINZXhhbXBsZTY4LmNvbYINZXhhbXBsZTY5
|
||||
LmNvbYINZXhhbXBsZTcwLmNvbYINZXhhbXBsZTcxLmNvbYINZXhhbXBsZTcyLmNv
|
||||
bYINZXhhbXBsZTczLmNvbYINZXhhbXBsZTc0LmNvbYINZXhhbXBsZTc1LmNvbYIN
|
||||
ZXhhbXBsZTc2LmNvbYINZXhhbXBsZTc3LmNvbYINZXhhbXBsZTc4LmNvbYINZXhh
|
||||
bXBsZTc5LmNvbYINZXhhbXBsZTgwLmNvbYINZXhhbXBsZTgxLmNvbYINZXhhbXBs
|
||||
ZTgyLmNvbYINZXhhbXBsZTgzLmNvbYINZXhhbXBsZTg0LmNvbYINZXhhbXBsZTg1
|
||||
LmNvbYINZXhhbXBsZTg2LmNvbYINZXhhbXBsZTg3LmNvbYINZXhhbXBsZTg4LmNv
|
||||
bYINZXhhbXBsZTg5LmNvbYINZXhhbXBsZTkwLmNvbYINZXhhbXBsZTkxLmNvbYIN
|
||||
ZXhhbXBsZTkyLmNvbYINZXhhbXBsZTkzLmNvbYINZXhhbXBsZTk0LmNvbYINZXhh
|
||||
bXBsZTk1LmNvbYINZXhhbXBsZTk2LmNvbYINZXhhbXBsZTk3LmNvbYINZXhhbXBs
|
||||
ZTk4LmNvbYINZXhhbXBsZTk5LmNvbYIOZXhhbXBsZTEwMC5jb20wDQYJKoZIhvcN
|
||||
AQELBQADQQBEunJbKUXcyNKTSfA0pKRyWNiKmkoBqYgfZS6eHNrNH/hjFzHtzyDQ
|
||||
XYHHK6kgEWBvHfRXGmqhFvht+b1tQKkG
|
||||
-----END CERTIFICATE-----
|
||||
30
acme/acme/testdata/cert-idnsans.pem
vendored
Normal file
30
acme/acme/testdata/cert-idnsans.pem
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIFNjCCBOCgAwIBAgIJAP4rNqqOKifCMA0GCSqGSIb3DQEBCwUAMGQxCzAJBgNV
|
||||
BAgMAkNBMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMScwJQYDVQQLDB5FbGVjdHJv
|
||||
bmljIEZyb250aWVyIEZvdW5kYXRpb24xFDASBgNVBAMMC2V4YW1wbGUuY29tMB4X
|
||||
DTE2MDEwNjIwMDg1OFoXDTE2MDEwNzIwMDg1OFowZDELMAkGA1UECAwCQ0ExFjAU
|
||||
BgNVBAcMDVNhbiBGcmFuY2lzY28xJzAlBgNVBAsMHkVsZWN0cm9uaWMgRnJvbnRp
|
||||
ZXIgRm91bmRhdGlvbjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wXDANBgkqhkiG9w0B
|
||||
AQEFAANLADBIAkEArHVztFHtH92ucFJD/N/HW9AsdRsUuHUBBBDlHwNlRd3fp580
|
||||
rv2+6QWE30cWgdmJS86ObRz6lUTor4R0T+3C5QIDAQABo4IDczCCA28wCQYDVR0T
|
||||
BAIwADALBgNVHQ8EBAMCBeAwggNTBgNVHREEggNKMIIDRoJiz4PPhM+Fz4bPh8+I
|
||||
z4nPis+Lz4zPjc+Oz4/PkM+Rz5LPk8+Uz5XPls+Xz5jPmc+az5vPnM+dz57Pn8+g
|
||||
z6HPos+jz6TPpc+mz6fPqM+pz6rPq8+sz63Prs+vLmludmFsaWSCYs+wz7HPss+z
|
||||
z7TPtc+2z7fPuM+5z7rPu8+8z73Pvs+/2YHZgtmD2YTZhdmG2YfZiNmJ2YrZi9mM
|
||||
2Y3ZjtmP2ZDZkdmS2ZPZlNmV2ZbZl9mY2ZnZmtmb2ZzZnS5pbnZhbGlkgmLZntmf
|
||||
2aDZodmi2aPZpNml2abZp9mo2anZqtmr2azZrdmu2a/ZsNmx2bLZs9m02bXZttm3
|
||||
2bjZudm62bvZvNm92b7Zv9qA2oHagtqD2oTahdqG2ofaiNqJ2oouaW52YWxpZIJi
|
||||
2ovajNqN2o7aj9qQ2pHaktqT2pTaldqW2pfamNqZ2pram9qc2p3antqf2qDaodqi
|
||||
2qPapNql2qbap9qo2qnaqtqr2qzardqu2q/asNqx2rLas9q02rXattq3LmludmFs
|
||||
aWSCYtq42rnautq72rzavdq+2r/bgNuB24Lbg9uE24XbhtuH24jbiduK24vbjNuN
|
||||
247bj9uQ25HbktuT25TblduW25fbmNuZ25rbm9uc253bntuf26Dbodui26PbpC5p
|
||||
bnZhbGlkgnjbpdum26fbqNup26rbq9us263brtuv27Dbsduy27PbtNu127bbt9u4
|
||||
27nbutu74aCg4aCh4aCi4aCj4aCk4aCl4aCm4aCn4aCo4aCp4aCq4aCr4aCs4aCt
|
||||
4aCu4aCv4aCw4aCx4aCy4aCz4aC04aC1LmludmFsaWSCgY/hoLbhoLfhoLjhoLnh
|
||||
oLrhoLvhoLzhoL3hoL7hoL/hoYDhoYHhoYLhoYPhoYThoYXhoYbhoYfhoYjhoYnh
|
||||
oYrhoYvhoYzhoY3hoY7hoY/hoZDhoZHhoZLhoZPhoZThoZXhoZbhoZfhoZjhoZnh
|
||||
oZrhoZvhoZzhoZ3hoZ7hoZ/hoaDhoaHhoaIuaW52YWxpZIJE4aGj4aGk4aGl4aGm
|
||||
4aGn4aGo4aGp4aGq4aGr4aGs4aGt4aGu4aGv4aGw4aGx4aGy4aGz4aG04aG14aG2
|
||||
LmludmFsaWQwDQYJKoZIhvcNAQELBQADQQAzOQL/54yXxln87/YvEQbBm9ik9zoT
|
||||
TxEkvnZ4kmTRhDsUPtRjMXhY2FH7LOtXKnJQ7POUB7AsJ2Z6uq2w623G
|
||||
-----END CERTIFICATE-----
|
||||
41
acme/acme/testdata/csr-100sans.pem
vendored
Normal file
41
acme/acme/testdata/csr-100sans.pem
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
-----BEGIN CERTIFICATE REQUEST-----
|
||||
MIIHNTCCBt8CAQAwZDELMAkGA1UECAwCQ0ExFjAUBgNVBAcMDVNhbiBGcmFuY2lz
|
||||
Y28xJzAlBgNVBAsMHkVsZWN0cm9uaWMgRnJvbnRpZXIgRm91bmRhdGlvbjEUMBIG
|
||||
A1UEAwwLZXhhbXBsZS5jb20wXDANBgkqhkiG9w0BAQEFAANLADBIAkEArHVztFHt
|
||||
H92ucFJD/N/HW9AsdRsUuHUBBBDlHwNlRd3fp580rv2+6QWE30cWgdmJS86ObRz6
|
||||
lUTor4R0T+3C5QIDAQABoIIGFDCCBhAGCSqGSIb3DQEJDjGCBgEwggX9MAkGA1Ud
|
||||
EwQCMAAwCwYDVR0PBAQDAgXgMIIF4QYDVR0RBIIF2DCCBdSCDGV4YW1wbGUxLmNv
|
||||
bYIMZXhhbXBsZTIuY29tggxleGFtcGxlMy5jb22CDGV4YW1wbGU0LmNvbYIMZXhh
|
||||
bXBsZTUuY29tggxleGFtcGxlNi5jb22CDGV4YW1wbGU3LmNvbYIMZXhhbXBsZTgu
|
||||
Y29tggxleGFtcGxlOS5jb22CDWV4YW1wbGUxMC5jb22CDWV4YW1wbGUxMS5jb22C
|
||||
DWV4YW1wbGUxMi5jb22CDWV4YW1wbGUxMy5jb22CDWV4YW1wbGUxNC5jb22CDWV4
|
||||
YW1wbGUxNS5jb22CDWV4YW1wbGUxNi5jb22CDWV4YW1wbGUxNy5jb22CDWV4YW1w
|
||||
bGUxOC5jb22CDWV4YW1wbGUxOS5jb22CDWV4YW1wbGUyMC5jb22CDWV4YW1wbGUy
|
||||
MS5jb22CDWV4YW1wbGUyMi5jb22CDWV4YW1wbGUyMy5jb22CDWV4YW1wbGUyNC5j
|
||||
b22CDWV4YW1wbGUyNS5jb22CDWV4YW1wbGUyNi5jb22CDWV4YW1wbGUyNy5jb22C
|
||||
DWV4YW1wbGUyOC5jb22CDWV4YW1wbGUyOS5jb22CDWV4YW1wbGUzMC5jb22CDWV4
|
||||
YW1wbGUzMS5jb22CDWV4YW1wbGUzMi5jb22CDWV4YW1wbGUzMy5jb22CDWV4YW1w
|
||||
bGUzNC5jb22CDWV4YW1wbGUzNS5jb22CDWV4YW1wbGUzNi5jb22CDWV4YW1wbGUz
|
||||
Ny5jb22CDWV4YW1wbGUzOC5jb22CDWV4YW1wbGUzOS5jb22CDWV4YW1wbGU0MC5j
|
||||
b22CDWV4YW1wbGU0MS5jb22CDWV4YW1wbGU0Mi5jb22CDWV4YW1wbGU0My5jb22C
|
||||
DWV4YW1wbGU0NC5jb22CDWV4YW1wbGU0NS5jb22CDWV4YW1wbGU0Ni5jb22CDWV4
|
||||
YW1wbGU0Ny5jb22CDWV4YW1wbGU0OC5jb22CDWV4YW1wbGU0OS5jb22CDWV4YW1w
|
||||
bGU1MC5jb22CDWV4YW1wbGU1MS5jb22CDWV4YW1wbGU1Mi5jb22CDWV4YW1wbGU1
|
||||
My5jb22CDWV4YW1wbGU1NC5jb22CDWV4YW1wbGU1NS5jb22CDWV4YW1wbGU1Ni5j
|
||||
b22CDWV4YW1wbGU1Ny5jb22CDWV4YW1wbGU1OC5jb22CDWV4YW1wbGU1OS5jb22C
|
||||
DWV4YW1wbGU2MC5jb22CDWV4YW1wbGU2MS5jb22CDWV4YW1wbGU2Mi5jb22CDWV4
|
||||
YW1wbGU2My5jb22CDWV4YW1wbGU2NC5jb22CDWV4YW1wbGU2NS5jb22CDWV4YW1w
|
||||
bGU2Ni5jb22CDWV4YW1wbGU2Ny5jb22CDWV4YW1wbGU2OC5jb22CDWV4YW1wbGU2
|
||||
OS5jb22CDWV4YW1wbGU3MC5jb22CDWV4YW1wbGU3MS5jb22CDWV4YW1wbGU3Mi5j
|
||||
b22CDWV4YW1wbGU3My5jb22CDWV4YW1wbGU3NC5jb22CDWV4YW1wbGU3NS5jb22C
|
||||
DWV4YW1wbGU3Ni5jb22CDWV4YW1wbGU3Ny5jb22CDWV4YW1wbGU3OC5jb22CDWV4
|
||||
YW1wbGU3OS5jb22CDWV4YW1wbGU4MC5jb22CDWV4YW1wbGU4MS5jb22CDWV4YW1w
|
||||
bGU4Mi5jb22CDWV4YW1wbGU4My5jb22CDWV4YW1wbGU4NC5jb22CDWV4YW1wbGU4
|
||||
NS5jb22CDWV4YW1wbGU4Ni5jb22CDWV4YW1wbGU4Ny5jb22CDWV4YW1wbGU4OC5j
|
||||
b22CDWV4YW1wbGU4OS5jb22CDWV4YW1wbGU5MC5jb22CDWV4YW1wbGU5MS5jb22C
|
||||
DWV4YW1wbGU5Mi5jb22CDWV4YW1wbGU5My5jb22CDWV4YW1wbGU5NC5jb22CDWV4
|
||||
YW1wbGU5NS5jb22CDWV4YW1wbGU5Ni5jb22CDWV4YW1wbGU5Ny5jb22CDWV4YW1w
|
||||
bGU5OC5jb22CDWV4YW1wbGU5OS5jb22CDmV4YW1wbGUxMDAuY29tMA0GCSqGSIb3
|
||||
DQEBCwUAA0EAW05UMFavHn2rkzMyUfzsOvWzVNlm43eO2yHu5h5TzDb23gkDnNEo
|
||||
duUAbQ+CLJHYd+MvRCmPQ+3ZnaPy7l/0Hg==
|
||||
-----END CERTIFICATE REQUEST-----
|
||||
27
acme/acme/testdata/csr-idnsans.pem
vendored
Normal file
27
acme/acme/testdata/csr-idnsans.pem
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
-----BEGIN CERTIFICATE REQUEST-----
|
||||
MIIEpzCCBFECAQAwZDELMAkGA1UECAwCQ0ExFjAUBgNVBAcMDVNhbiBGcmFuY2lz
|
||||
Y28xJzAlBgNVBAsMHkVsZWN0cm9uaWMgRnJvbnRpZXIgRm91bmRhdGlvbjEUMBIG
|
||||
A1UEAwwLZXhhbXBsZS5jb20wXDANBgkqhkiG9w0BAQEFAANLADBIAkEArHVztFHt
|
||||
H92ucFJD/N/HW9AsdRsUuHUBBBDlHwNlRd3fp580rv2+6QWE30cWgdmJS86ObRz6
|
||||
lUTor4R0T+3C5QIDAQABoIIDhjCCA4IGCSqGSIb3DQEJDjGCA3MwggNvMAkGA1Ud
|
||||
EwQCMAAwCwYDVR0PBAQDAgXgMIIDUwYDVR0RBIIDSjCCA0aCYs+Dz4TPhc+Gz4fP
|
||||
iM+Jz4rPi8+Mz43Pjs+Pz5DPkc+Sz5PPlM+Vz5bPl8+Yz5nPms+bz5zPnc+ez5/P
|
||||
oM+hz6LPo8+kz6XPps+nz6jPqc+qz6vPrM+tz67Pry5pbnZhbGlkgmLPsM+xz7LP
|
||||
s8+0z7XPts+3z7jPuc+6z7vPvM+9z77Pv9mB2YLZg9mE2YXZhtmH2YjZidmK2YvZ
|
||||
jNmN2Y7Zj9mQ2ZHZktmT2ZTZldmW2ZfZmNmZ2ZrZm9mc2Z0uaW52YWxpZIJi2Z7Z
|
||||
n9mg2aHZotmj2aTZpdmm2afZqNmp2arZq9ms2a3Zrtmv2bDZsdmy2bPZtNm12bbZ
|
||||
t9m42bnZutm72bzZvdm+2b/agNqB2oLag9qE2oXahtqH2ojaidqKLmludmFsaWSC
|
||||
YtqL2ozajdqO2o/akNqR2pLak9qU2pXaltqX2pjamdqa2pvanNqd2p7an9qg2qHa
|
||||
otqj2qTapdqm2qfaqNqp2qraq9qs2q3artqv2rDasdqy2rPatNq12rbaty5pbnZh
|
||||
bGlkgmLauNq52rrau9q82r3avtq/24DbgduC24PbhNuF24bbh9uI24nbituL24zb
|
||||
jduO24/bkNuR25Lbk9uU25XbltuX25jbmdua25vbnNud257bn9ug26Hbotuj26Qu
|
||||
aW52YWxpZIJ426Xbptun26jbqduq26vbrNut267br9uw27Hbstuz27Tbtdu227fb
|
||||
uNu527rbu+GgoOGgoeGgouGgo+GgpOGgpeGgpuGgp+GgqOGgqeGgquGgq+GgrOGg
|
||||
reGgruGgr+GgsOGgseGgsuGgs+GgtOGgtS5pbnZhbGlkgoGP4aC24aC34aC44aC5
|
||||
4aC64aC74aC84aC94aC+4aC/4aGA4aGB4aGC4aGD4aGE4aGF4aGG4aGH4aGI4aGJ
|
||||
4aGK4aGL4aGM4aGN4aGO4aGP4aGQ4aGR4aGS4aGT4aGU4aGV4aGW4aGX4aGY4aGZ
|
||||
4aGa4aGb4aGc4aGd4aGe4aGf4aGg4aGh4aGiLmludmFsaWSCROGho+GhpOGhpeGh
|
||||
puGhp+GhqOGhqeGhquGhq+GhrOGhreGhruGhr+GhsOGhseGhsuGhs+GhtOGhteGh
|
||||
ti5pbnZhbGlkMA0GCSqGSIb3DQEBCwUAA0EAeNkY0M0+kMnjRo6dEUoGE4dX9fEr
|
||||
dfGrpPUBcwG0P5QBdZJWvZxTfRl14yuPYHbGHULXeGqRdkU6HK5pOlzpng==
|
||||
-----END CERTIFICATE REQUEST-----
|
||||
@@ -6,12 +6,13 @@ from setuptools import find_packages
|
||||
|
||||
version = '0.2.0.dev0'
|
||||
|
||||
# Please update tox.ini when modifying dependency version requirements
|
||||
install_requires = [
|
||||
# load_pem_private/public_key (>=0.6)
|
||||
# rsa_recover_prime_factors (>=0.8)
|
||||
'cryptography>=0.8',
|
||||
# Connection.set_tlsext_host_name (>=0.13), X509Req.get_extensions (>=0.15)
|
||||
'PyOpenSSL>=0.15',
|
||||
# Connection.set_tlsext_host_name (>=0.13)
|
||||
'PyOpenSSL>=0.13',
|
||||
'pyrfc3339',
|
||||
'pytz',
|
||||
'requests',
|
||||
@@ -31,7 +32,7 @@ else:
|
||||
install_requires.append('mock')
|
||||
|
||||
if sys.version_info < (2, 7, 9):
|
||||
# For secure SSL connexion with Python 2.7 (InsecurePlatformWarning)
|
||||
# For secure SSL connection with Python 2.7 (InsecurePlatformWarning)
|
||||
install_requires.append('ndg-httpsclient')
|
||||
install_requires.append('pyasn1')
|
||||
|
||||
@@ -66,6 +67,7 @@ setup(
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.3',
|
||||
'Programming Language :: Python :: 3.4',
|
||||
'Programming Language :: Python :: 3.5',
|
||||
'Topic :: Internet :: WWW/HTTP',
|
||||
'Topic :: Security',
|
||||
],
|
||||
|
||||
@@ -32,28 +32,42 @@ if apt-cache show python-virtualenv > /dev/null 2>&1; then
|
||||
virtualenv="$virtualenv python-virtualenv"
|
||||
fi
|
||||
|
||||
augeas_pkg=libaugeas0
|
||||
augeas_pkg="libaugeas0 augeas-lenses"
|
||||
AUGVERSION=`apt-cache show --no-all-versions libaugeas0 | grep ^Version: | cut -d" " -f2`
|
||||
|
||||
AddBackportRepo() {
|
||||
# ARGS:
|
||||
BACKPORT_NAME="$1"
|
||||
BACKPORT_SOURCELINE="$2"
|
||||
if ! grep -v -e ' *#' /etc/apt/sources.list | grep -q "$BACKPORT_NAME" ; then
|
||||
# This can theoretically error if sources.list.d is empty, but in that case we don't care.
|
||||
if ! grep -v -e ' *#' /etc/apt/sources.list.d/* 2>/dev/null | grep -q "$BACKPORT_NAME"; then
|
||||
/bin/echo -n "Installing augeas from $BACKPORT_NAME in 3 seconds..."
|
||||
sleep 1s
|
||||
/bin/echo -ne "\e[0K\rInstalling augeas from $BACKPORT_NAME in 2 seconds..."
|
||||
sleep 1s
|
||||
/bin/echo -e "\e[0K\rInstalling augeas from $BACKPORT_NAME in 1 second ..."
|
||||
sleep 1s
|
||||
if echo $BACKPORT_NAME | grep -q wheezy ; then
|
||||
/bin/echo '(Backports are only installed if explicitly requested via "apt-get install -t wheezy-backports")'
|
||||
fi
|
||||
|
||||
echo $BACKPORT_SOURCELINE >> /etc/apt/sources.list.d/"$BACKPORT_NAME".list
|
||||
apt-get update
|
||||
fi
|
||||
fi
|
||||
apt-get install -y --no-install-recommends -t "$BACKPORT_NAME" $augeas_pkg
|
||||
augeas_pkg=
|
||||
|
||||
}
|
||||
|
||||
|
||||
if dpkg --compare-versions 1.0 gt "$AUGVERSION" ; then
|
||||
if lsb_release -a | grep -q wheezy ; then
|
||||
if ! grep -v -e ' *#' /etc/apt/sources.list | grep -q wheezy-backports ; then
|
||||
# This can theoretically error if sources.list.d is empty, but in that case we don't care.
|
||||
if ! grep -v -e ' *#' /etc/apt/sources.list.d/* 2>/dev/null | grep -q wheezy-backports ; then
|
||||
/bin/echo -n "Installing augeas from wheezy-backports in 3 seconds..."
|
||||
sleep 1s
|
||||
/bin/echo -ne "\e[0K\rInstalling augeas from wheezy-backports in 2 seconds..."
|
||||
sleep 1s
|
||||
/bin/echo -e "\e[0K\rInstalling augeas from wheezy-backports in 1 second ..."
|
||||
sleep 1s
|
||||
/bin/echo '(Backports are only installed if explicitly requested via "apt-get install -t wheezy-backports")'
|
||||
|
||||
echo deb http://http.debian.net/debian wheezy-backports main >> /etc/apt/sources.list.d/wheezy-backports.list
|
||||
apt-get update
|
||||
fi
|
||||
fi
|
||||
apt-get install -y --no-install-recommends -t wheezy-backports libaugeas0
|
||||
augeas_pkg=
|
||||
AddBackportRepo wheezy-backports "deb http://http.debian.net/debian wheezy-backports main"
|
||||
elif lsb_release -a | grep -q precise ; then
|
||||
# XXX add ARM case
|
||||
AddBackportRepo precise-backports "deb http://archive.ubuntu.com/ubuntu precise-backports main restricted universe multiverse"
|
||||
else
|
||||
echo "No libaugeas0 version is available that's new enough to run the"
|
||||
echo "Let's Encrypt apache plugin..."
|
||||
|
||||
@@ -371,10 +371,12 @@ are provided here mainly for the :ref:`developers <hacking>` reference.
|
||||
In general:
|
||||
|
||||
* ``sudo`` is required as a suggested way of running privileged process
|
||||
* `Python`_ 2.6/2.7 is required
|
||||
* `Augeas`_ is required for the Python bindings
|
||||
* ``virtualenv`` and ``pip`` are used for managing other python library
|
||||
dependencies
|
||||
|
||||
.. _Python: https://wiki.python.org/moin/BeginnersGuide/Download
|
||||
.. _Augeas: http://augeas.net/
|
||||
.. _Virtualenv: https://virtualenv.pypa.io
|
||||
|
||||
|
||||
@@ -139,9 +139,20 @@ Would obtain a single certificate for all of those names, using the
|
||||
``/var/www/example`` webroot directory for the first two, and
|
||||
``/var/www/eg`` for the second two.
|
||||
|
||||
The webroot plugin works by creating a temporary file for each of your requested
|
||||
domains in ``${webroot-path}/.well-known/acme-challenge``. Then the Let's
|
||||
Encrypt validation server makes HTTP requests to validate that the DNS for each
|
||||
requested domain resolves to the server running letsencrypt. An example request
|
||||
made to your web server would look like:
|
||||
|
||||
::
|
||||
|
||||
66.133.109.36 - - [05/Jan/2016:20:11:24 -0500] "GET /.well-known/acme-challenge/HGr8U1IeTW4kY_Z6UIyaakzOkyQgPr_7ArlLgtZE8SX HTTP/1.1" 200 87 "-" "Mozilla/5.0 (compatible; Let's Encrypt validation server; +https://www.letsencrypt.org)"
|
||||
|
||||
Note that to use the webroot plugin, your server must be configured to serve
|
||||
files from hidden directories.
|
||||
|
||||
|
||||
Manual
|
||||
------
|
||||
|
||||
@@ -237,7 +248,9 @@ The following files are available:
|
||||
server certificate, i.e. root and intermediate certificates only.
|
||||
|
||||
This is what Apache < 2.4.8 needs for `SSLCertificateChainFile
|
||||
<https://httpd.apache.org/docs/2.4/mod/mod_ssl.html#sslcertificatechainfile>`_.
|
||||
<https://httpd.apache.org/docs/2.4/mod/mod_ssl.html#sslcertificatechainfile>`_,
|
||||
and what nginx >= 1.3.7 needs for `ssl_trusted_certificate
|
||||
<http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_trusted_certificate>`_.
|
||||
|
||||
``fullchain.pem``
|
||||
All certificates, **including** server certificate. This is
|
||||
|
||||
@@ -558,6 +558,8 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
||||
# In case no Listens are set (which really is a broken apache config)
|
||||
if not listens:
|
||||
listens = ["80"]
|
||||
if port in listens:
|
||||
return
|
||||
for listen in listens:
|
||||
# For any listen statement, check if the machine also listens on Port 443.
|
||||
# If not, add such a listen statement.
|
||||
@@ -1311,6 +1313,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
||||
|
||||
"""
|
||||
self.config_test()
|
||||
logger.debug(self.reverter.view_config_changes(for_logging=True))
|
||||
self._reload()
|
||||
|
||||
def _reload(self):
|
||||
|
||||
@@ -35,6 +35,7 @@ class ApacheParser(object):
|
||||
# https://httpd.apache.org/docs/2.4/mod/core.html#define
|
||||
# https://httpd.apache.org/docs/2.4/mod/core.html#ifdefine
|
||||
# This only handles invocation parameters and Define directives!
|
||||
self.parser_paths = {}
|
||||
self.variables = {}
|
||||
if version >= (2, 4):
|
||||
self.update_runtime_variables()
|
||||
@@ -471,16 +472,63 @@ class ApacheParser(object):
|
||||
:param str filepath: Apache config file path
|
||||
|
||||
"""
|
||||
use_new, remove_old = self._check_path_actions(filepath)
|
||||
# Test if augeas included file for Httpd.lens
|
||||
# Note: This works for augeas globs, ie. *.conf
|
||||
inc_test = self.aug.match(
|
||||
"/augeas/load/Httpd/incl [. ='%s']" % filepath)
|
||||
if not inc_test:
|
||||
# Load up files
|
||||
# This doesn't seem to work on TravisCI
|
||||
# self.aug.add_transform("Httpd.lns", [filepath])
|
||||
self._add_httpd_transform(filepath)
|
||||
self.aug.load()
|
||||
if use_new:
|
||||
inc_test = self.aug.match(
|
||||
"/augeas/load/Httpd/incl [. ='%s']" % filepath)
|
||||
if not inc_test:
|
||||
# Load up files
|
||||
# This doesn't seem to work on TravisCI
|
||||
# self.aug.add_transform("Httpd.lns", [filepath])
|
||||
if remove_old:
|
||||
self._remove_httpd_transform(filepath)
|
||||
self._add_httpd_transform(filepath)
|
||||
self.aug.load()
|
||||
|
||||
def _check_path_actions(self, filepath):
|
||||
"""Determine actions to take with a new augeas path
|
||||
|
||||
This helper function will return a tuple that defines
|
||||
if we should try to append the new filepath to augeas
|
||||
parser paths, and / or remove the old one with more
|
||||
narrow matching.
|
||||
|
||||
:param str filepath: filepath to check the actions for
|
||||
|
||||
"""
|
||||
|
||||
try:
|
||||
new_file_match = os.path.basename(filepath)
|
||||
existing_matches = self.parser_paths[os.path.dirname(filepath)]
|
||||
if "*" in existing_matches:
|
||||
use_new = False
|
||||
else:
|
||||
use_new = True
|
||||
if new_file_match == "*":
|
||||
remove_old = True
|
||||
else:
|
||||
remove_old = False
|
||||
except KeyError:
|
||||
use_new = True
|
||||
remove_old = False
|
||||
return use_new, remove_old
|
||||
|
||||
def _remove_httpd_transform(self, filepath):
|
||||
"""Remove path from Augeas transform
|
||||
|
||||
:param str filepath: filepath to remove
|
||||
"""
|
||||
|
||||
remove_basenames = self.parser_paths[os.path.dirname(filepath)]
|
||||
remove_dirname = os.path.dirname(filepath)
|
||||
for name in remove_basenames:
|
||||
remove_path = remove_dirname + "/" + name
|
||||
remove_inc = self.aug.match(
|
||||
"/augeas/load/Httpd/incl [. ='%s']" % remove_path)
|
||||
self.aug.remove(remove_inc[0])
|
||||
self.parser_paths.pop(remove_dirname)
|
||||
|
||||
def _add_httpd_transform(self, incl):
|
||||
"""Add a transform to Augeas.
|
||||
@@ -502,6 +550,13 @@ class ApacheParser(object):
|
||||
# Augeas uses base 1 indexing... insert at beginning...
|
||||
self.aug.set("/augeas/load/Httpd/lens", "Httpd.lns")
|
||||
self.aug.set("/augeas/load/Httpd/incl", incl)
|
||||
# Add included path to paths dictionary
|
||||
try:
|
||||
self.parser_paths[os.path.dirname(incl)].append(
|
||||
os.path.basename(incl))
|
||||
except KeyError:
|
||||
self.parser_paths[os.path.dirname(incl)] = [
|
||||
os.path.basename(incl)]
|
||||
|
||||
def standardize_excl(self):
|
||||
"""Standardize the excl arguments for the Httpd lens in Augeas.
|
||||
|
||||
@@ -49,7 +49,8 @@ if [ "$1" = --debian-modules ] ; then
|
||||
sudo apt-get install -y libapache2-mod-wsgi
|
||||
sudo apt-get install -y libapache2-mod-macro
|
||||
|
||||
for mod in ssl rewrite macro wsgi deflate userdir version mime ; do
|
||||
for mod in ssl rewrite macro wsgi deflate userdir version mime setenvif ; do
|
||||
echo -n enabling $mod
|
||||
sudo a2enmod $mod
|
||||
done
|
||||
fi
|
||||
|
||||
@@ -461,6 +461,25 @@ class TwoVhost80Test(util.ApacheTest):
|
||||
self.assertEqual(mock_add_dir.call_args_list[1][0][2], ["[::1]:8080", "https"])
|
||||
self.assertEqual(mock_add_dir.call_args_list[2][0][2], ["1.1.1.1:8080", "https"])
|
||||
|
||||
def test_prepare_server_https_mixed_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", "443"]
|
||||
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
|
||||
|
||||
# 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, 0)
|
||||
|
||||
def test_make_vhost_ssl(self):
|
||||
ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[0])
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ class BasicParserTest(util.ParserTest):
|
||||
|
||||
"""
|
||||
file_path = os.path.join(
|
||||
self.config_path, "sites-available", "letsencrypt.conf")
|
||||
self.config_path, "not-parsed-by-default", "letsencrypt.conf")
|
||||
|
||||
self.parser._parse_file(file_path) # pylint: disable=protected-access
|
||||
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
"""A class that performs TLS-SNI-01 challenges for Apache"""
|
||||
|
||||
import os
|
||||
import logging
|
||||
|
||||
from letsencrypt.plugins import common
|
||||
|
||||
from letsencrypt_apache import obj
|
||||
from letsencrypt_apache import parser
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class ApacheTlsSni01(common.TLSSNI01):
|
||||
"""Class that performs TLS-SNI-01 challenges within the Apache configurator
|
||||
@@ -104,6 +106,7 @@ class ApacheTlsSni01(common.TLSSNI01):
|
||||
self.configurator.reverter.register_file_creation(
|
||||
True, self.challenge_conf)
|
||||
|
||||
logger.debug("writing a config file with text: %s", config_text)
|
||||
with open(self.challenge_conf, "w") as new_conf:
|
||||
new_conf.write(config_text)
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ from setuptools import find_packages
|
||||
|
||||
version = '0.2.0.dev0'
|
||||
|
||||
# Please update tox.ini when modifying dependency version requirements
|
||||
install_requires = [
|
||||
'acme=={0}'.format(version),
|
||||
'letsencrypt=={0}'.format(version),
|
||||
|
||||
@@ -122,7 +122,7 @@ class NginxConfigurator(common.Plugin):
|
||||
|
||||
# Entry point in main.py for installing cert
|
||||
def deploy_cert(self, domain, cert_path, key_path,
|
||||
chain_path, fullchain_path):
|
||||
chain_path=None, fullchain_path=None):
|
||||
# pylint: disable=unused-argument
|
||||
"""Deploys certificate to specified virtual host.
|
||||
|
||||
@@ -136,7 +136,15 @@ class NginxConfigurator(common.Plugin):
|
||||
|
||||
.. note:: This doesn't save the config files!
|
||||
|
||||
:raises errors.PluginError: When unable to deploy certificate due to
|
||||
a lack of directives or configuration
|
||||
|
||||
"""
|
||||
if not fullchain_path:
|
||||
raise errors.PluginError(
|
||||
"The nginx plugin currently requires --fullchain-path to "
|
||||
"install a cert.")
|
||||
|
||||
vhost = self.choose_vhost(domain)
|
||||
cert_directives = [['ssl_certificate', fullchain_path],
|
||||
['ssl_certificate_key', key_path]]
|
||||
@@ -150,6 +158,12 @@ class NginxConfigurator(common.Plugin):
|
||||
['ssl_stapling', 'on'],
|
||||
['ssl_stapling_verify', 'on']]
|
||||
|
||||
if len(stapling_directives) != 0 and not chain_path:
|
||||
raise errors.PluginError(
|
||||
"--chain-path is required to enable "
|
||||
"Online Certificate Status Protocol (OCSP) stapling "
|
||||
"on nginx >= 1.3.7.")
|
||||
|
||||
try:
|
||||
self.parser.add_server_directives(vhost.filep, vhost.names,
|
||||
cert_directives, replace=True)
|
||||
@@ -168,7 +182,7 @@ class NginxConfigurator(common.Plugin):
|
||||
self.save_notes += ("Changed vhost at %s with addresses of %s\n" %
|
||||
(vhost.filep,
|
||||
", ".join(str(addr) for addr in vhost.addrs)))
|
||||
self.save_notes += "\tssl_certificate %s\n" % cert_path
|
||||
self.save_notes += "\tssl_certificate %s\n" % fullchain_path
|
||||
self.save_notes += "\tssl_certificate_key %s\n" % key_path
|
||||
|
||||
#######################
|
||||
@@ -311,17 +325,11 @@ class NginxConfigurator(common.Plugin):
|
||||
"""
|
||||
snakeoil_cert, snakeoil_key = self._get_snakeoil_paths()
|
||||
ssl_block = [['listen', '{0} ssl'.format(self.config.tls_sni_01_port)],
|
||||
# access and error logs necessary for integration
|
||||
# testing (non-root)
|
||||
['access_log', os.path.join(
|
||||
self.config.work_dir, 'access.log')],
|
||||
['error_log', os.path.join(
|
||||
self.config.work_dir, 'error.log')],
|
||||
['ssl_certificate', snakeoil_cert],
|
||||
['ssl_certificate_key', snakeoil_key],
|
||||
['include', self.parser.loc["ssl_options"]]]
|
||||
self.parser.add_server_directives(
|
||||
vhost.filep, vhost.names, ssl_block)
|
||||
vhost.filep, vhost.names, ssl_block, replace=False)
|
||||
vhost.ssl = True
|
||||
vhost.raw.extend(ssl_block)
|
||||
vhost.addrs.add(obj.Addr(
|
||||
@@ -384,7 +392,7 @@ class NginxConfigurator(common.Plugin):
|
||||
[['return', '301 https://$host$request_uri']]
|
||||
]]
|
||||
self.parser.add_server_directives(
|
||||
vhost.filep, vhost.names, redirect_block)
|
||||
vhost.filep, vhost.names, redirect_block, replace=False)
|
||||
logger.info("Redirecting all traffic to ssl in %s", vhost.filep)
|
||||
|
||||
######################################
|
||||
@@ -393,11 +401,10 @@ class NginxConfigurator(common.Plugin):
|
||||
def restart(self):
|
||||
"""Restarts nginx server.
|
||||
|
||||
:returns: Success
|
||||
:rtype: bool
|
||||
:raises .errors.MisconfigurationError: If either the reload fails.
|
||||
|
||||
"""
|
||||
return nginx_restart(self.conf('ctl'), self.nginx_conf)
|
||||
nginx_restart(self.conf('ctl'), self.nginx_conf)
|
||||
|
||||
def config_test(self): # pylint: disable=no-self-use
|
||||
"""Check the configuration of Nginx for errors.
|
||||
@@ -631,19 +638,16 @@ def nginx_restart(nginx_ctl, nginx_conf="/etc/nginx.conf"):
|
||||
|
||||
if nginx_proc.returncode != 0:
|
||||
# Enter recovery routine...
|
||||
logger.error("Nginx Restart Failed!\n%s\n%s", stdout, stderr)
|
||||
return False
|
||||
raise errors.MisconfigurationError(
|
||||
"nginx restart failed:\n%s\n%s" % (stdout, stderr))
|
||||
|
||||
except (OSError, ValueError):
|
||||
logger.fatal("Nginx Restart Failed - Please Check the Configuration")
|
||||
sys.exit(1)
|
||||
raise errors.MisconfigurationError("nginx restart failed")
|
||||
# Nginx can take a moment to recognize a newly added TLS SNI servername, so sleep
|
||||
# for a second. TODO: Check for expected servername and loop until it
|
||||
# appears or return an error if looping too long.
|
||||
time.sleep(1)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def temp_install(options_ssl):
|
||||
"""Temporary install for convenience."""
|
||||
|
||||
@@ -113,7 +113,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)
|
||||
parsed_server = parse_server(server)
|
||||
vhost = obj.VirtualHost(filename,
|
||||
parsed_server['addrs'],
|
||||
parsed_server['ssl'],
|
||||
@@ -213,6 +213,7 @@ class NginxParser(object):
|
||||
if ext:
|
||||
filename = filename + os.path.extsep + ext
|
||||
try:
|
||||
logger.debug('Dumping to %s:\n%s', filename, nginxparser.dumps(tree))
|
||||
with open(filename, 'w') as _file:
|
||||
nginxparser.dump(tree, _file)
|
||||
except IOError:
|
||||
@@ -252,7 +253,7 @@ class NginxParser(object):
|
||||
return server_names == names
|
||||
|
||||
def add_server_directives(self, filename, names, directives,
|
||||
replace=False):
|
||||
replace):
|
||||
"""Add or replace directives in the first server block with names.
|
||||
|
||||
..note :: If replace is True, this raises a misconfiguration error
|
||||
@@ -269,20 +270,27 @@ class NginxParser(object):
|
||||
:param bool replace: Whether to only replace existing directives
|
||||
|
||||
"""
|
||||
_do_for_subarray(self.parsed[filename],
|
||||
lambda x: self._has_server_names(x, names),
|
||||
lambda x: _add_directives(x, directives, replace))
|
||||
try:
|
||||
_do_for_subarray(self.parsed[filename],
|
||||
lambda x: self._has_server_names(x, names),
|
||||
lambda x: _add_directives(x, directives, replace))
|
||||
except errors.MisconfigurationError as err:
|
||||
raise errors.MisconfigurationError("Problem in %s: %s" % (filename, err.message))
|
||||
|
||||
def add_http_directives(self, filename, directives):
|
||||
"""Adds directives to the first encountered HTTP block in filename.
|
||||
|
||||
We insert new directives at the top of the block to work around
|
||||
https://trac.nginx.org/nginx/ticket/810: If the first server block
|
||||
doesn't enable OCSP stapling, stapling is broken for all blocks.
|
||||
|
||||
:param str filename: The absolute filename of the config file
|
||||
:param list directives: The directives to add
|
||||
|
||||
"""
|
||||
_do_for_subarray(self.parsed[filename],
|
||||
lambda x: x[0] == ['http'],
|
||||
lambda x: _add_directives(x[1], [directives], False))
|
||||
lambda x: x[1].insert(0, directives))
|
||||
|
||||
def get_all_certs_keys(self):
|
||||
"""Gets all certs and keys in the nginx config.
|
||||
@@ -443,7 +451,7 @@ def _get_servernames(names):
|
||||
return names.split(' ')
|
||||
|
||||
|
||||
def _parse_server(server):
|
||||
def parse_server(server):
|
||||
"""Parses a list of server directives.
|
||||
|
||||
:param list server: list of directives in a server block
|
||||
@@ -463,13 +471,20 @@ def _parse_server(server):
|
||||
elif directive[0] == 'server_name':
|
||||
parsed_server['names'].update(
|
||||
_get_servernames(directive[1]))
|
||||
elif directive[0] == 'ssl' and directive[1] == 'on':
|
||||
parsed_server['ssl'] = True
|
||||
|
||||
return parsed_server
|
||||
|
||||
|
||||
def _add_directives(block, directives, replace=False):
|
||||
"""Adds or replaces directives in a block. If the directive doesn't exist in
|
||||
the entry already, raises a misconfiguration error.
|
||||
def _add_directives(block, directives, replace):
|
||||
"""Adds or replaces directives in a config block.
|
||||
|
||||
When replace=False, it's an error to try and add a directive that already
|
||||
exists in the config block with a conflicting value.
|
||||
|
||||
When replace=True, a directive with the same name MUST already exist in the
|
||||
config block, and the first instance will be replaced.
|
||||
|
||||
..todo :: Find directives that are in included files.
|
||||
|
||||
@@ -478,21 +493,43 @@ def _add_directives(block, directives, replace=False):
|
||||
|
||||
"""
|
||||
for directive in directives:
|
||||
if not replace:
|
||||
# We insert new directives at the top of the block, mostly
|
||||
# to work around https://trac.nginx.org/nginx/ticket/810
|
||||
# Only add directive if its not already in the block
|
||||
if directive not in block:
|
||||
block.insert(0, directive)
|
||||
else:
|
||||
changed = False
|
||||
if len(directive) == 0:
|
||||
continue
|
||||
for index, line in enumerate(block):
|
||||
if len(line) > 0 and line[0] == directive[0]:
|
||||
block[index] = directive
|
||||
changed = True
|
||||
if not changed:
|
||||
_add_directive(block, directive, replace)
|
||||
|
||||
repeatable_directives = set(['server_name', 'listen', 'include'])
|
||||
|
||||
def _add_directive(block, directive, replace):
|
||||
"""Adds or replaces a single directive in a config block.
|
||||
|
||||
See _add_directives for more documentation.
|
||||
|
||||
"""
|
||||
location = -1
|
||||
# Find the index of a config line where the name of the directive matches
|
||||
# the name of the directive we want to add.
|
||||
for index, line in enumerate(block):
|
||||
if len(line) > 0 and line[0] == directive[0]:
|
||||
location = index
|
||||
break
|
||||
if replace:
|
||||
if location == -1:
|
||||
raise errors.MisconfigurationError(
|
||||
'expected directive for %s in the Nginx '
|
||||
'config but did not find it.' % directive[0])
|
||||
block[location] = directive
|
||||
else:
|
||||
# Append directive. Fail if the name is not a repeatable directive name,
|
||||
# and there is already a copy of that directive with a different value
|
||||
# in the config file.
|
||||
directive_name = directive[0]
|
||||
directive_value = directive[1]
|
||||
if location != -1 and directive_name.__str__() not in repeatable_directives:
|
||||
if block[location][1] == directive_value:
|
||||
# There's a conflict, but the existing value matches the one we
|
||||
# want to insert, so it's fine.
|
||||
pass
|
||||
else:
|
||||
raise errors.MisconfigurationError(
|
||||
'Let\'s Encrypt expected directive for %s in the Nginx '
|
||||
'config but did not find it.' % directive[0])
|
||||
'tried to insert directive "%s" but found conflicting "%s".' % (
|
||||
directive, block[location]))
|
||||
else:
|
||||
block.append(directive)
|
||||
|
||||
@@ -40,6 +40,23 @@ class NginxConfiguratorTest(util.NginxTest):
|
||||
self.assertEquals((1, 6, 2), self.config.version)
|
||||
self.assertEquals(5, len(self.config.parser.parsed))
|
||||
|
||||
@mock.patch("letsencrypt_nginx.configurator.le_util.exe_exists")
|
||||
@mock.patch("letsencrypt_nginx.configurator.subprocess.Popen")
|
||||
def test_prepare_initializes_version(self, mock_popen, mock_exe_exists):
|
||||
mock_popen().communicate.return_value = (
|
||||
"", "\n".join(["nginx version: nginx/1.6.2",
|
||||
"built by clang 6.0 (clang-600.0.56)"
|
||||
" (based on LLVM 3.5svn)",
|
||||
"TLS SNI support enabled",
|
||||
"configure arguments: --prefix=/usr/local/Cellar/"
|
||||
"nginx/1.6.2 --with-http_ssl_module"]))
|
||||
|
||||
mock_exe_exists.return_value = True
|
||||
|
||||
self.config.version = None
|
||||
self.config.prepare()
|
||||
self.assertEquals((1, 6, 2), self.config.version)
|
||||
|
||||
@mock.patch("letsencrypt_nginx.configurator.socket.gethostbyaddr")
|
||||
def test_get_all_names(self, mock_gethostbyaddr):
|
||||
mock_gethostbyaddr.return_value = ('155.225.50.69.nephoscale.net', [], [])
|
||||
@@ -65,16 +82,19 @@ 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()
|
||||
|
||||
# pylint: disable=protected-access
|
||||
parsed = self.config.parser._parse_files(filep, override=True)
|
||||
self.assertEqual([[['server'], [['listen', '5001 ssl'],
|
||||
self.assertEqual([[['server'], [
|
||||
['listen', '69.50.225.155:9000'],
|
||||
['listen', '127.0.0.1'],
|
||||
['server_name', '.example.com'],
|
||||
['server_name', 'example.*']]]],
|
||||
['server_name', 'example.*'],
|
||||
['listen', '5001 ssl']
|
||||
]]],
|
||||
parsed[0])
|
||||
|
||||
def test_choose_vhost(self):
|
||||
@@ -91,12 +111,26 @@ class NginxConfiguratorTest(util.NginxTest):
|
||||
'test.www.example.com': foo_conf,
|
||||
'abc.www.foo.com': foo_conf,
|
||||
'www.bar.co.uk': localhost_conf}
|
||||
|
||||
conf_path = {'localhost': "etc_nginx/nginx.conf",
|
||||
'alias': "etc_nginx/nginx.conf",
|
||||
'example.com': "etc_nginx/sites-enabled/example.com",
|
||||
'example.com.uk.test': "etc_nginx/sites-enabled/example.com",
|
||||
'www.example.com': "etc_nginx/sites-enabled/example.com",
|
||||
'test.www.example.com': "etc_nginx/foo.conf",
|
||||
'abc.www.foo.com': "etc_nginx/foo.conf",
|
||||
'www.bar.co.uk': "etc_nginx/nginx.conf"}
|
||||
|
||||
bad_results = ['www.foo.com', 'example', 't.www.bar.co',
|
||||
'69.255.225.155']
|
||||
|
||||
for name in results:
|
||||
self.assertEqual(results[name],
|
||||
self.config.choose_vhost(name).names)
|
||||
vhost = self.config.choose_vhost(name)
|
||||
path = os.path.relpath(vhost.filep, self.temp_dir)
|
||||
|
||||
self.assertEqual(results[name], vhost.names)
|
||||
self.assertEqual(conf_path[name], path)
|
||||
|
||||
for name in bad_results:
|
||||
self.assertEqual(set([name]), self.config.choose_vhost(name).names)
|
||||
|
||||
@@ -125,6 +159,24 @@ class NginxConfiguratorTest(util.NginxTest):
|
||||
self.assertTrue(util.contains_at_depth(generated_conf,
|
||||
['ssl_trusted_certificate', 'example/chain.pem'], 2))
|
||||
|
||||
def test_deploy_cert_stapling_requires_chain_path(self):
|
||||
self.config.version = (1, 3, 7)
|
||||
self.assertRaises(errors.PluginError, self.config.deploy_cert,
|
||||
"www.example.com",
|
||||
"example/cert.pem",
|
||||
"example/key.pem",
|
||||
None,
|
||||
"example/fullchain.pem")
|
||||
|
||||
def test_deploy_cert_requires_fullchain_path(self):
|
||||
self.config.version = (1, 3, 1)
|
||||
self.assertRaises(errors.PluginError, self.config.deploy_cert,
|
||||
"www.example.com",
|
||||
"example/cert.pem",
|
||||
"example/key.pem",
|
||||
"example/chain.pem",
|
||||
None)
|
||||
|
||||
def test_deploy_cert(self):
|
||||
server_conf = self.config.parser.abs_path('server.conf')
|
||||
nginx_conf = self.config.parser.abs_path('nginx.conf')
|
||||
@@ -154,38 +206,36 @@ class NginxConfiguratorTest(util.NginxTest):
|
||||
parsed_server_conf = util.filter_comments(self.config.parser.parsed[server_conf])
|
||||
parsed_nginx_conf = util.filter_comments(self.config.parser.parsed[nginx_conf])
|
||||
|
||||
access_log = os.path.join(self.work_dir, "access.log")
|
||||
error_log = os.path.join(self.work_dir, "error.log")
|
||||
self.assertEqual([[['server'],
|
||||
[['include', self.config.parser.loc["ssl_options"]],
|
||||
['ssl_certificate_key', 'example/key.pem'],
|
||||
['ssl_certificate', 'example/fullchain.pem'],
|
||||
['error_log', error_log],
|
||||
['access_log', access_log],
|
||||
|
||||
['listen', '5001 ssl'],
|
||||
[
|
||||
['listen', '69.50.225.155:9000'],
|
||||
['listen', '127.0.0.1'],
|
||||
['server_name', '.example.com'],
|
||||
['server_name', 'example.*']]]],
|
||||
['server_name', 'example.*'],
|
||||
|
||||
['listen', '5001 ssl'],
|
||||
['ssl_certificate', 'example/fullchain.pem'],
|
||||
['ssl_certificate_key', 'example/key.pem'],
|
||||
['include', self.config.parser.loc["ssl_options"]]
|
||||
]]],
|
||||
parsed_example_conf)
|
||||
self.assertEqual([['server_name', 'somename alias another.alias']],
|
||||
parsed_server_conf)
|
||||
self.assertTrue(util.contains_at_depth(parsed_nginx_conf,
|
||||
[['server'],
|
||||
[['include', self.config.parser.loc["ssl_options"]],
|
||||
['ssl_certificate_key', '/etc/nginx/key.pem'],
|
||||
['ssl_certificate', '/etc/nginx/fullchain.pem'],
|
||||
['error_log', error_log],
|
||||
['access_log', access_log],
|
||||
['listen', '5001 ssl'],
|
||||
['listen', '8000'],
|
||||
['listen', 'somename:8080'],
|
||||
['include', 'server.conf'],
|
||||
[['location', '/'],
|
||||
[['root', 'html'],
|
||||
['index', 'index.html index.htm']]]]],
|
||||
2))
|
||||
self.assertTrue(util.contains_at_depth(
|
||||
parsed_nginx_conf,
|
||||
[['server'],
|
||||
[
|
||||
['listen', '8000'],
|
||||
['listen', 'somename:8080'],
|
||||
['include', 'server.conf'],
|
||||
[['location', '/'],
|
||||
[['root', 'html'],
|
||||
['index', 'index.html index.htm']]],
|
||||
['listen', '5001 ssl'],
|
||||
['ssl_certificate', '/etc/nginx/fullchain.pem'],
|
||||
['ssl_certificate_key', '/etc/nginx/key.pem'],
|
||||
['include', self.config.parser.loc["ssl_options"]]]],
|
||||
2))
|
||||
|
||||
def test_get_all_certs_keys(self):
|
||||
nginx_conf = self.config.parser.abs_path('nginx.conf')
|
||||
@@ -297,19 +347,19 @@ class NginxConfiguratorTest(util.NginxTest):
|
||||
mocked = mock_popen()
|
||||
mocked.communicate.return_value = ('', '')
|
||||
mocked.returncode = 0
|
||||
self.assertTrue(self.config.restart())
|
||||
self.config.restart()
|
||||
|
||||
@mock.patch("letsencrypt_nginx.configurator.subprocess.Popen")
|
||||
def test_nginx_restart_fail(self, mock_popen):
|
||||
mocked = mock_popen()
|
||||
mocked.communicate.return_value = ('', '')
|
||||
mocked.returncode = 1
|
||||
self.assertFalse(self.config.restart())
|
||||
self.assertRaises(errors.MisconfigurationError, self.config.restart)
|
||||
|
||||
@mock.patch("letsencrypt_nginx.configurator.subprocess.Popen")
|
||||
def test_no_nginx_start(self, mock_popen):
|
||||
mock_popen.side_effect = OSError("Can't find program")
|
||||
self.assertRaises(SystemExit, self.config.restart)
|
||||
self.assertRaises(errors.MisconfigurationError, self.config.restart)
|
||||
|
||||
@mock.patch("letsencrypt_nginx.configurator.subprocess.Popen")
|
||||
def test_config_test(self, mock_popen):
|
||||
@@ -330,6 +380,17 @@ class NginxConfiguratorTest(util.NginxTest):
|
||||
OpenSSL.crypto.load_privatekey(
|
||||
OpenSSL.crypto.FILETYPE_PEM, key_file.read())
|
||||
|
||||
def test_redirect_enhance(self):
|
||||
expected = [
|
||||
['if', '($scheme != "https")'],
|
||||
[['return', '301 https://$host$request_uri']]
|
||||
]
|
||||
|
||||
example_conf = self.config.parser.abs_path('sites-enabled/example.com')
|
||||
self.config.enhance("www.example.com", "redirect")
|
||||
|
||||
generated_conf = self.config.parser.parsed[example_conf]
|
||||
self.assertTrue(util.contains_at_depth(generated_conf, expected, 2))
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main() # pragma: no cover
|
||||
|
||||
@@ -127,7 +127,8 @@ class NginxParserTest(util.NginxTest):
|
||||
set(['localhost',
|
||||
r'~^(www\.)?(example|bar)\.']),
|
||||
[['foo', 'bar'], ['ssl_certificate',
|
||||
'/etc/ssl/cert.pem']])
|
||||
'/etc/ssl/cert.pem']],
|
||||
replace=False)
|
||||
ssl_re = re.compile(r'\n\s+ssl_certificate /etc/ssl/cert.pem')
|
||||
dump = nginxparser.dumps(nparser.parsed[nparser.abs_path('nginx.conf')])
|
||||
self.assertEqual(1, len(re.findall(ssl_re, dump)))
|
||||
@@ -136,12 +137,15 @@ class NginxParserTest(util.NginxTest):
|
||||
names = set(['alias', 'another.alias', 'somename'])
|
||||
nparser.add_server_directives(server_conf, names,
|
||||
[['foo', 'bar'], ['ssl_certificate',
|
||||
'/etc/ssl/cert2.pem']])
|
||||
nparser.add_server_directives(server_conf, names, [['foo', 'bar']])
|
||||
'/etc/ssl/cert2.pem']],
|
||||
replace=False)
|
||||
nparser.add_server_directives(server_conf, names, [['foo', 'bar']],
|
||||
replace=False)
|
||||
self.assertEqual(nparser.parsed[server_conf],
|
||||
[['ssl_certificate', '/etc/ssl/cert2.pem'],
|
||||
[['server_name', 'somename alias another.alias'],
|
||||
['foo', 'bar'],
|
||||
['server_name', 'somename alias another.alias']])
|
||||
['ssl_certificate', '/etc/ssl/cert2.pem']
|
||||
])
|
||||
|
||||
def test_add_http_directives(self):
|
||||
nparser = parser.NginxParser(self.config_path, self.ssl_options)
|
||||
@@ -165,17 +169,19 @@ class NginxParserTest(util.NginxTest):
|
||||
target = set(['.example.com', 'example.*'])
|
||||
filep = nparser.abs_path('sites-enabled/example.com')
|
||||
nparser.add_server_directives(
|
||||
filep, target, [['server_name', 'foo bar']], True)
|
||||
filep, target, [['server_name', 'foobar.com']], replace=True)
|
||||
self.assertEqual(
|
||||
nparser.parsed[filep],
|
||||
[[['server'], [['listen', '69.50.225.155:9000'],
|
||||
['listen', '127.0.0.1'],
|
||||
['server_name', 'foo bar'],
|
||||
['server_name', 'foo bar']]]])
|
||||
['server_name', 'foobar.com'],
|
||||
['server_name', 'example.*'],
|
||||
]]])
|
||||
self.assertRaises(errors.MisconfigurationError,
|
||||
nparser.add_server_directives,
|
||||
filep, set(['foo', 'bar']),
|
||||
[['ssl_certificate', 'cert.pem']], True)
|
||||
filep, set(['foobar.com', 'example.*']),
|
||||
[['ssl_certificate', 'cert.pem']],
|
||||
replace=True)
|
||||
|
||||
def test_get_best_match(self):
|
||||
target_name = 'www.eff.org'
|
||||
@@ -217,10 +223,31 @@ class NginxParserTest(util.NginxTest):
|
||||
set(['.example.com', 'example.*']),
|
||||
[['ssl_certificate', 'foo.pem'],
|
||||
['ssl_certificate_key', 'bar.key'],
|
||||
['listen', '443 ssl']])
|
||||
['listen', '443 ssl']],
|
||||
replace=False)
|
||||
c_k = nparser.get_all_certs_keys()
|
||||
self.assertEqual(set([('foo.pem', 'bar.key', filep)]), c_k)
|
||||
|
||||
def test_parse_server_ssl(self):
|
||||
server = parser.parse_server([
|
||||
['listen', '443']
|
||||
])
|
||||
self.assertFalse(server['ssl'])
|
||||
|
||||
server = parser.parse_server([
|
||||
['listen', '443 ssl']
|
||||
])
|
||||
self.assertTrue(server['ssl'])
|
||||
|
||||
server = parser.parse_server([
|
||||
['listen', '443'], ['ssl', 'off']
|
||||
])
|
||||
self.assertFalse(server['ssl'])
|
||||
|
||||
server = parser.parse_server([
|
||||
['listen', '443'], ['ssl', 'on']
|
||||
])
|
||||
self.assertTrue(server['ssl'])
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main() # pragma: no cover
|
||||
|
||||
@@ -6,6 +6,7 @@ from setuptools import find_packages
|
||||
|
||||
version = '0.2.0.dev0'
|
||||
|
||||
# Please update tox.ini when modifying dependency version requirements
|
||||
install_requires = [
|
||||
'acme=={0}'.format(version),
|
||||
'letsencrypt=={0}'.format(version),
|
||||
|
||||
@@ -20,13 +20,14 @@ events {
|
||||
}
|
||||
|
||||
http {
|
||||
# Set an array of temp and cache file options that will otherwise default to
|
||||
# Set an array of temp, cache and log file options that will otherwise default to
|
||||
# restricted locations accessible only to root.
|
||||
client_body_temp_path $root/client_body;
|
||||
fastcgi_temp_path $root/fastcgi_temp;
|
||||
proxy_temp_path $root/proxy_temp;
|
||||
#scgi_temp_path $root/scgi_temp;
|
||||
#uwsgi_temp_path $root/uwsgi_temp;
|
||||
access_log $root/error.log;
|
||||
|
||||
# This should be turned off in a Virtualbox VM, as it can cause some
|
||||
# interesting issues with data corruption in delivered files.
|
||||
@@ -54,9 +55,6 @@ http {
|
||||
|
||||
root $root/webroot;
|
||||
|
||||
access_log $root/access.log;
|
||||
error_log $root/error.log;
|
||||
|
||||
location / {
|
||||
# First attempt to serve request as file, then as directory, then fall
|
||||
# back to index.html.
|
||||
|
||||
@@ -540,16 +540,11 @@ def _generate_failed_chall_msg(failed_achalls):
|
||||
|
||||
"""
|
||||
typ = failed_achalls[0].error.typ
|
||||
msg = [
|
||||
"The following '{0}' errors were reported by the server:".format(typ)]
|
||||
msg = ["The following errors were reported by the server:"]
|
||||
|
||||
problems = dict()
|
||||
for achall in failed_achalls:
|
||||
problems.setdefault(achall.error.description, set()).add(achall.domain)
|
||||
for problem in problems:
|
||||
msg.append("\n\nDomains: ")
|
||||
msg.append(", ".join(sorted(problems[problem])))
|
||||
msg.append("\nError: {0}".format(problem))
|
||||
msg.append("\n\nDomain: %s\nType: %s\nDetail: %s" % (
|
||||
achall.domain, achall.error.typ, achall.error.detail))
|
||||
|
||||
if typ in _ERROR_HELP:
|
||||
msg.append("\n\n")
|
||||
|
||||
@@ -392,7 +392,7 @@ def _auth_from_domains(le_client, config, domains):
|
||||
# TODO: Check whether it worked! <- or make sure errors are thrown (jdk)
|
||||
lineage.save_successor(
|
||||
lineage.latest_common_version(), OpenSSL.crypto.dump_certificate(
|
||||
OpenSSL.crypto.FILETYPE_PEM, new_certr.body),
|
||||
OpenSSL.crypto.FILETYPE_PEM, new_certr.body.wrapped),
|
||||
new_key.pem, crypto_util.dump_pyopenssl_chain(new_chain))
|
||||
|
||||
lineage.update_all_links_to(lineage.latest_common_version())
|
||||
|
||||
@@ -300,7 +300,7 @@ class Client(object):
|
||||
|
||||
lineage = storage.RenewableCert.new_lineage(
|
||||
domains[0], OpenSSL.crypto.dump_certificate(
|
||||
OpenSSL.crypto.FILETYPE_PEM, certr.body),
|
||||
OpenSSL.crypto.FILETYPE_PEM, certr.body.wrapped),
|
||||
key.pem, crypto_util.dump_pyopenssl_chain(chain),
|
||||
params, config, cli_config)
|
||||
return lineage
|
||||
@@ -330,7 +330,7 @@ class Client(object):
|
||||
self.config.strict_permissions)
|
||||
|
||||
cert_pem = OpenSSL.crypto.dump_certificate(
|
||||
OpenSSL.crypto.FILETYPE_PEM, certr.body)
|
||||
OpenSSL.crypto.FILETYPE_PEM, certr.body.wrapped)
|
||||
cert_file, act_cert_path = le_util.unique_file(cert_path, 0o644)
|
||||
try:
|
||||
cert_file.write(cert_pem)
|
||||
|
||||
@@ -271,7 +271,7 @@ def dump_pyopenssl_chain(chain, filetype=OpenSSL.crypto.FILETYPE_PEM):
|
||||
def _dump_cert(cert):
|
||||
if isinstance(cert, jose.ComparableX509):
|
||||
# pylint: disable=protected-access
|
||||
cert = cert._wrapped
|
||||
cert = cert.wrapped
|
||||
return OpenSSL.crypto.dump_certificate(filetype, cert)
|
||||
|
||||
# assumes that OpenSSL.crypto.dump_certificate includes ending
|
||||
|
||||
@@ -150,7 +150,7 @@ class Authenticator(common.Plugin):
|
||||
|
||||
# one self-signed key for all tls-sni-01 certificates
|
||||
self.key = OpenSSL.crypto.PKey()
|
||||
self.key.generate_key(OpenSSL.crypto.TYPE_RSA, bits=2048)
|
||||
self.key.generate_key(OpenSSL.crypto.TYPE_RSA, 2048)
|
||||
|
||||
self.served = collections.defaultdict(set)
|
||||
|
||||
|
||||
@@ -103,7 +103,7 @@ def renew(cert, old_version):
|
||||
# already understands this distinction!)
|
||||
return cert.save_successor(
|
||||
old_version, OpenSSL.crypto.dump_certificate(
|
||||
OpenSSL.crypto.FILETYPE_PEM, new_certr.body),
|
||||
OpenSSL.crypto.FILETYPE_PEM, new_certr.body.wrapped),
|
||||
new_key.pem, crypto_util.dump_pyopenssl_chain(new_chain))
|
||||
# TODO: Notify results
|
||||
else:
|
||||
|
||||
@@ -94,7 +94,7 @@ class Reverter(object):
|
||||
"Unable to load checkpoint during rollback")
|
||||
rollback -= 1
|
||||
|
||||
def view_config_changes(self):
|
||||
def view_config_changes(self, for_logging=False):
|
||||
"""Displays all saved checkpoints.
|
||||
|
||||
All checkpoints are printed by
|
||||
@@ -144,6 +144,8 @@ class Reverter(object):
|
||||
|
||||
output.append(os.linesep)
|
||||
|
||||
if for_logging:
|
||||
return os.linesep.join(output)
|
||||
zope.component.getUtility(interfaces.IDisplay).notification(
|
||||
os.linesep.join(output), display_util.HEIGHT)
|
||||
|
||||
|
||||
@@ -467,7 +467,7 @@ class ReportFailedChallsTest(unittest.TestCase):
|
||||
auth_handler._report_failed_challs([self.http01, self.tls_sni_same])
|
||||
call_list = mock_zope().add_message.call_args_list
|
||||
self.assertTrue(len(call_list) == 1)
|
||||
self.assertTrue("Domains: example.com\n" in call_list[0][0][0])
|
||||
self.assertTrue("Domain: example.com\nType: tls\nDetail: detail" in call_list[0][0][0])
|
||||
|
||||
@mock.patch("letsencrypt.auth_handler.zope.component.getUtility")
|
||||
def test_different_errors_and_domains(self, mock_zope):
|
||||
|
||||
@@ -417,11 +417,11 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
|
||||
chain_path = '/etc/letsencrypt/live/foo.bar/fullchain.pem'
|
||||
|
||||
mock_lineage = mock.MagicMock(cert=cert_path, fullchain=chain_path)
|
||||
mock_cert = mock.MagicMock(body='body')
|
||||
mock_certr = mock.MagicMock()
|
||||
mock_key = mock.MagicMock(pem='pem_key')
|
||||
mock_renewal.return_value = ("renew", mock_lineage)
|
||||
mock_client = mock.MagicMock()
|
||||
mock_client.obtain_certificate.return_value = (mock_cert, 'chain',
|
||||
mock_client.obtain_certificate.return_value = (mock_certr, 'chain',
|
||||
mock_key, 'csr')
|
||||
mock_init.return_value = mock_client
|
||||
with mock.patch('letsencrypt.cli.OpenSSL'):
|
||||
|
||||
@@ -141,9 +141,9 @@ class ClientTest(unittest.TestCase):
|
||||
tmp_path = tempfile.mkdtemp()
|
||||
os.chmod(tmp_path, 0o755) # TODO: really??
|
||||
|
||||
certr = mock.MagicMock(body=test_util.load_cert(certs[0]))
|
||||
chain_cert = [test_util.load_cert(certs[1]),
|
||||
test_util.load_cert(certs[2])]
|
||||
certr = mock.MagicMock(body=test_util.load_comparable_cert(certs[0]))
|
||||
chain_cert = [test_util.load_comparable_cert(certs[1]),
|
||||
test_util.load_comparable_cert(certs[2])]
|
||||
candidate_cert_path = os.path.join(tmp_path, "certs", "cert.pem")
|
||||
candidate_chain_path = os.path.join(tmp_path, "chains", "chain.pem")
|
||||
candidate_fullchain_path = os.path.join(tmp_path, "chains", "fullchain.pem")
|
||||
|
||||
@@ -9,6 +9,8 @@ import unittest
|
||||
import configobj
|
||||
import mock
|
||||
|
||||
from acme import jose
|
||||
|
||||
from letsencrypt import configuration
|
||||
from letsencrypt import errors
|
||||
from letsencrypt.storage import ALL_FOUR
|
||||
@@ -702,9 +704,10 @@ class RenewableCertTests(BaseRenewableCertTest):
|
||||
self.test_rc.configfile["renewalparams"]["authenticator"] = "apache"
|
||||
mock_client = mock.MagicMock()
|
||||
# pylint: disable=star-args
|
||||
comparable_cert = jose.ComparableX509(CERT)
|
||||
mock_client.obtain_certificate.return_value = (
|
||||
mock.MagicMock(body=CERT), [CERT], mock.Mock(pem="key"),
|
||||
mock.sentinel.csr)
|
||||
mock.MagicMock(body=comparable_cert), [comparable_cert],
|
||||
mock.Mock(pem="key"), mock.sentinel.csr)
|
||||
mock_c.return_value = mock_client
|
||||
self.assertEqual(2, renewer.renew(self.test_rc, 1))
|
||||
# TODO: We could also make several assertions about calls that should
|
||||
|
||||
@@ -40,16 +40,24 @@ def load_cert(*names):
|
||||
"""Load certificate."""
|
||||
loader = _guess_loader(
|
||||
names[-1], OpenSSL.crypto.FILETYPE_PEM, OpenSSL.crypto.FILETYPE_ASN1)
|
||||
return jose.ComparableX509(OpenSSL.crypto.load_certificate(
|
||||
loader, load_vector(*names)))
|
||||
return OpenSSL.crypto.load_certificate(loader, load_vector(*names))
|
||||
|
||||
|
||||
def load_comparable_cert(*names):
|
||||
"""Load ComparableX509 cert."""
|
||||
return jose.ComparableX509(load_cert(*names))
|
||||
|
||||
|
||||
def load_csr(*names):
|
||||
"""Load certificate request."""
|
||||
loader = _guess_loader(
|
||||
names[-1], OpenSSL.crypto.FILETYPE_PEM, OpenSSL.crypto.FILETYPE_ASN1)
|
||||
return jose.ComparableX509(OpenSSL.crypto.load_certificate_request(
|
||||
loader, load_vector(*names)))
|
||||
return OpenSSL.crypto.load_certificate_request(loader, load_vector(*names))
|
||||
|
||||
|
||||
def load_comparable_csr(*names):
|
||||
"""Load ComparableX509 certificate request."""
|
||||
return jose.ComparableX509(load_csr(*names))
|
||||
|
||||
|
||||
def load_rsa_private_key(*names):
|
||||
|
||||
1
setup.py
1
setup.py
@@ -30,6 +30,7 @@ readme = read_file(os.path.join(here, 'README.rst'))
|
||||
changes = read_file(os.path.join(here, 'CHANGES.rst'))
|
||||
version = meta['version']
|
||||
|
||||
# Please update tox.ini when modifying dependency version requirements
|
||||
install_requires = [
|
||||
'acme=={0}'.format(version),
|
||||
'configobj',
|
||||
|
||||
@@ -77,6 +77,15 @@ parser.add_argument('--saveinstances',
|
||||
parser.add_argument('--alt_pip',
|
||||
default='',
|
||||
help="server from which to pull candidate release packages")
|
||||
parser.add_argument('--killboulder',
|
||||
action='store_true',
|
||||
help="do not leave a persistent boulder server running")
|
||||
parser.add_argument('--boulderonly',
|
||||
action='store_true',
|
||||
help="only make a boulder server")
|
||||
parser.add_argument('--fast',
|
||||
action='store_true',
|
||||
help="use larger instance types to run faster (saves about a minute, probably not worth it)")
|
||||
cl_args = parser.parse_args()
|
||||
|
||||
# Credential Variables
|
||||
@@ -292,6 +301,30 @@ def grab_letsencrypt_log():
|
||||
sudo('if [ -f ./letsencrypt.log ]; then \
|
||||
cat ./letsencrypt.log; else echo "[nolocallog]"; fi')
|
||||
|
||||
def create_client_instances(targetlist):
|
||||
"Create a fleet of client instances"
|
||||
instances = []
|
||||
print("Creating instances: ", end="")
|
||||
for target in targetlist:
|
||||
if target['virt'] == 'hvm':
|
||||
machine_type = 't2.medium' if cl_args.fast else 't2.micro'
|
||||
else:
|
||||
# 32 bit systems
|
||||
machine_type = 'c1.medium' if cl_args.fast else 't1.micro'
|
||||
if 'userdata' in target.keys():
|
||||
userdata = target['userdata']
|
||||
else:
|
||||
userdata = ''
|
||||
name = 'le-%s'%target['name']
|
||||
print(name, end=" ")
|
||||
instances.append(make_instance(name,
|
||||
target['ami'],
|
||||
KEYNAME,
|
||||
machine_type=machine_type,
|
||||
userdata=userdata))
|
||||
print()
|
||||
return instances
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# SCRIPT BEGINS
|
||||
#-------------------------------------------------------------------------------
|
||||
@@ -352,30 +385,28 @@ if not sg_exists:
|
||||
make_security_group()
|
||||
time.sleep(30)
|
||||
|
||||
boulder_preexists = False
|
||||
boulder_servers = EC2.instances.filter(Filters=[
|
||||
{'Name': 'tag:Name', 'Values': ['le-boulderserver']},
|
||||
{'Name': 'instance-state-name', 'Values': ['running']}])
|
||||
|
||||
boulder_server = next(iter(boulder_servers), None)
|
||||
|
||||
print("Requesting Instances...")
|
||||
boulder_server = make_instance('le-boulderserver',
|
||||
BOULDER_AMI,
|
||||
KEYNAME,
|
||||
#machine_type='t2.micro',
|
||||
machine_type='t2.medium',
|
||||
security_groups=['letsencrypt_test'])
|
||||
|
||||
instances = []
|
||||
for target in targetlist:
|
||||
if target['virt'] == 'hvm':
|
||||
machine_type = 't2.micro'
|
||||
else:
|
||||
machine_type = 't1.micro'
|
||||
if 'userdata' in target.keys():
|
||||
userdata = target['userdata']
|
||||
else:
|
||||
userdata = ''
|
||||
instances.append(make_instance('le-%s'%target['name'],
|
||||
target['ami'],
|
||||
if boulder_server:
|
||||
print("Found existing boulder server:", boulder_server)
|
||||
boulder_preexists = True
|
||||
else:
|
||||
print("Can't find a boulder server, starting one...")
|
||||
boulder_server = make_instance('le-boulderserver',
|
||||
BOULDER_AMI,
|
||||
KEYNAME,
|
||||
machine_type=machine_type,
|
||||
userdata=userdata))
|
||||
machine_type='t2.micro',
|
||||
#machine_type='t2.medium',
|
||||
security_groups=['letsencrypt_test'])
|
||||
|
||||
if not cl_args.boulderonly:
|
||||
instances = create_client_instances(targetlist)
|
||||
|
||||
# Configure and launch boulder server
|
||||
#-------------------------------------------------------------------------------
|
||||
@@ -383,21 +414,24 @@ print("Waiting on Boulder Server")
|
||||
boulder_server = block_until_instance_ready(boulder_server)
|
||||
print(" server %s"%boulder_server)
|
||||
|
||||
print("Configuring and Launching Boulder")
|
||||
|
||||
# env.host_string defines the ssh user and host for connection
|
||||
env.host_string = "ubuntu@%s"%boulder_server.public_ip_address
|
||||
print("Boulder Server at (SSH):", env.host_string)
|
||||
config_and_launch_boulder(boulder_server)
|
||||
# blocking often unnecessary, but cheap EC2 VMs can get very slow
|
||||
block_until_http_ready('http://%s:4000'%boulder_server.public_ip_address,
|
||||
wait_time=10,
|
||||
timeout=500)
|
||||
if not boulder_preexists:
|
||||
print("Configuring and Launching Boulder")
|
||||
config_and_launch_boulder(boulder_server)
|
||||
# blocking often unnecessary, but cheap EC2 VMs can get very slow
|
||||
block_until_http_ready('http://%s:4000'%boulder_server.public_ip_address,
|
||||
wait_time=10, timeout=500)
|
||||
|
||||
boulder_url = "http://%s:4000/directory"%boulder_server.private_ip_address
|
||||
print("Boulder Server at (public ip): http://%s:4000/directory"%boulder_server.public_ip_address)
|
||||
print("Boulder Server at (EC2 private ip): %s"%boulder_url)
|
||||
|
||||
if cl_args.boulderonly:
|
||||
sys.exit(0)
|
||||
|
||||
# Install and launch client scripts in parallel
|
||||
#-------------------------------------------------------------------------------
|
||||
print("Uploading and running test script in parallel: %s"%cl_args.test_script)
|
||||
@@ -480,7 +514,8 @@ results_file.close()
|
||||
if not cl_args.saveinstances:
|
||||
print('Logs in ', LOGDIR)
|
||||
print('Terminating EC2 Instances and Cleaning Dangling EBS Volumes')
|
||||
boulder_server.terminate()
|
||||
if cl_args.killboulder:
|
||||
boulder_server.terminate()
|
||||
terminate_and_clean(instances)
|
||||
else:
|
||||
# print login information for the boxes for debugging
|
||||
|
||||
@@ -7,7 +7,9 @@ cd letsencrypt
|
||||
#git checkout v0.1.0 use --branch instead
|
||||
SAVE="$PIP_EXTRA_INDEX_URL"
|
||||
unset PIP_EXTRA_INDEX_URL
|
||||
export PIP_INDEX_URL="https://isnot.org/pip/0.1.0/"
|
||||
./letsencrypt-auto -v --debug --version
|
||||
unset PIP_INDEX_URL
|
||||
|
||||
export PIP_EXTRA_INDEX_URL="$SAVE"
|
||||
|
||||
|
||||
@@ -4,4 +4,4 @@
|
||||
|
||||
cd letsencrypt
|
||||
# help installs virtualenv and does nothing else
|
||||
./letsencrypt-auto -v --help all
|
||||
./letsencrypt-auto -v --debug --help all
|
||||
|
||||
11
tox.ini
11
tox.ini
@@ -6,7 +6,7 @@
|
||||
# acme and letsencrypt are not yet on pypi, so when Tox invokes
|
||||
# "install *.zip", it will not find deps
|
||||
skipsdist = true
|
||||
envlist = py26,py27,py33,py34,py35,cover,lint
|
||||
envlist = py{26,27,33,34,35},py{26,27}-oldest,cover,lint
|
||||
|
||||
# nosetest -v => more verbose output, allows to detect busy waiting
|
||||
# loops, especially on Travis
|
||||
@@ -31,6 +31,13 @@ setenv =
|
||||
PYTHONHASHSEED = 0
|
||||
# https://testrun.org/tox/latest/example/basic.html#special-handling-of-pythonhas
|
||||
|
||||
deps =
|
||||
py{26,27}-oldest: cryptography==0.8
|
||||
py{26,27}-oldest: configargparse==0.10.0
|
||||
py{26,27}-oldest: psutil==2.1.0
|
||||
py{26,27}-oldest: PyOpenSSL==0.13
|
||||
py{26,27}-oldest: python2-pythondialog==3.2.2rc1
|
||||
|
||||
[testenv:py33]
|
||||
commands =
|
||||
pip install -e acme[testing]
|
||||
@@ -62,7 +69,7 @@ commands =
|
||||
pip install -e acme -e .[dev] -e letsencrypt-apache -e letsencrypt-nginx -e letsencrypt-compatibility-test -e letshelp-letsencrypt
|
||||
./pep8.travis.sh
|
||||
pylint --rcfile=.pylintrc letsencrypt
|
||||
pylint --rcfile=.pylintrc acme/acme
|
||||
pylint --rcfile=acme/.pylintrc acme/acme
|
||||
pylint --rcfile=.pylintrc letsencrypt-apache/letsencrypt_apache
|
||||
pylint --rcfile=.pylintrc letsencrypt-nginx/letsencrypt_nginx
|
||||
pylint --rcfile=.pylintrc letsencrypt-compatibility-test/letsencrypt_compatibility_test
|
||||
|
||||
Reference in New Issue
Block a user